editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    cx.set_state(indoc! {"
 4734        «implement-windows-supportˇ»
 4735    "});
 4736    cx.update_editor(|e, window, cx| {
 4737        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4738    });
 4739    cx.assert_editor_state(indoc! {"
 4740        «Implement windows supportˇ»
 4741    "});
 4742}
 4743
 4744#[gpui::test]
 4745async fn test_manipulate_text(cx: &mut TestAppContext) {
 4746    init_test(cx, |_| {});
 4747
 4748    let mut cx = EditorTestContext::new(cx).await;
 4749
 4750    // Test convert_to_upper_case()
 4751    cx.set_state(indoc! {"
 4752        «hello worldˇ»
 4753    "});
 4754    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4755    cx.assert_editor_state(indoc! {"
 4756        «HELLO WORLDˇ»
 4757    "});
 4758
 4759    // Test convert_to_lower_case()
 4760    cx.set_state(indoc! {"
 4761        «HELLO WORLDˇ»
 4762    "});
 4763    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4764    cx.assert_editor_state(indoc! {"
 4765        «hello worldˇ»
 4766    "});
 4767
 4768    // Test multiple line, single selection case
 4769    cx.set_state(indoc! {"
 4770        «The quick brown
 4771        fox jumps over
 4772        the lazy dogˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        «The Quick Brown
 4777        Fox Jumps Over
 4778        The Lazy Dogˇ»
 4779    "});
 4780
 4781    // Test multiple line, single selection case
 4782    cx.set_state(indoc! {"
 4783        «The quick brown
 4784        fox jumps over
 4785        the lazy dogˇ»
 4786    "});
 4787    cx.update_editor(|e, window, cx| {
 4788        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4789    });
 4790    cx.assert_editor_state(indoc! {"
 4791        «TheQuickBrown
 4792        FoxJumpsOver
 4793        TheLazyDogˇ»
 4794    "});
 4795
 4796    // From here on out, test more complex cases of manipulate_text()
 4797
 4798    // Test no selection case - should affect words cursors are in
 4799    // Cursor at beginning, middle, and end of word
 4800    cx.set_state(indoc! {"
 4801        ˇhello big beauˇtiful worldˇ
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4806    "});
 4807
 4808    // Test multiple selections on a single line and across multiple lines
 4809    cx.set_state(indoc! {"
 4810        «Theˇ» quick «brown
 4811        foxˇ» jumps «overˇ»
 4812        the «lazyˇ» dog
 4813    "});
 4814    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4815    cx.assert_editor_state(indoc! {"
 4816        «THEˇ» quick «BROWN
 4817        FOXˇ» jumps «OVERˇ»
 4818        the «LAZYˇ» dog
 4819    "});
 4820
 4821    // Test case where text length grows
 4822    cx.set_state(indoc! {"
 4823        «tschüߡ»
 4824    "});
 4825    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4826    cx.assert_editor_state(indoc! {"
 4827        «TSCHÜSSˇ»
 4828    "});
 4829
 4830    // Test to make sure we don't crash when text shrinks
 4831    cx.set_state(indoc! {"
 4832        aaa_bbbˇ
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «aaaBbbˇ»
 4839    "});
 4840
 4841    // Test to make sure we all aware of the fact that each word can grow and shrink
 4842    // Final selections should be aware of this fact
 4843    cx.set_state(indoc! {"
 4844        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4845    "});
 4846    cx.update_editor(|e, window, cx| {
 4847        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4848    });
 4849    cx.assert_editor_state(indoc! {"
 4850        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4851    "});
 4852
 4853    cx.set_state(indoc! {"
 4854        «hElLo, WoRld!ˇ»
 4855    "});
 4856    cx.update_editor(|e, window, cx| {
 4857        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4858    });
 4859    cx.assert_editor_state(indoc! {"
 4860        «HeLlO, wOrLD!ˇ»
 4861    "});
 4862}
 4863
 4864#[gpui::test]
 4865fn test_duplicate_line(cx: &mut TestAppContext) {
 4866    init_test(cx, |_| {});
 4867
 4868    let editor = cx.add_window(|window, cx| {
 4869        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4870        build_editor(buffer, window, cx)
 4871    });
 4872    _ = editor.update(cx, |editor, window, cx| {
 4873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4874            s.select_display_ranges([
 4875                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4876                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4877                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4878                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4879            ])
 4880        });
 4881        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4882        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4883        assert_eq!(
 4884            editor.selections.display_ranges(cx),
 4885            vec![
 4886                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4887                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4888                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4889                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4890            ]
 4891        );
 4892    });
 4893
 4894    let editor = cx.add_window(|window, cx| {
 4895        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4896        build_editor(buffer, window, cx)
 4897    });
 4898    _ = editor.update(cx, |editor, window, cx| {
 4899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4900            s.select_display_ranges([
 4901                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4902                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4903            ])
 4904        });
 4905        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4906        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4907        assert_eq!(
 4908            editor.selections.display_ranges(cx),
 4909            vec![
 4910                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4911                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4912            ]
 4913        );
 4914    });
 4915
 4916    // With `move_upwards` the selections stay in place, except for
 4917    // the lines inserted above them
 4918    let editor = cx.add_window(|window, cx| {
 4919        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4920        build_editor(buffer, window, cx)
 4921    });
 4922    _ = editor.update(cx, |editor, window, cx| {
 4923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4924            s.select_display_ranges([
 4925                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4926                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4927                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4928                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4929            ])
 4930        });
 4931        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4932        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4933        assert_eq!(
 4934            editor.selections.display_ranges(cx),
 4935            vec![
 4936                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4937                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4938                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4939                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4962            ]
 4963        );
 4964    });
 4965
 4966    let editor = cx.add_window(|window, cx| {
 4967        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4968        build_editor(buffer, window, cx)
 4969    });
 4970    _ = editor.update(cx, |editor, window, cx| {
 4971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4972            s.select_display_ranges([
 4973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4974                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4975            ])
 4976        });
 4977        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4978        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4979        assert_eq!(
 4980            editor.selections.display_ranges(cx),
 4981            vec![
 4982                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4983                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4984            ]
 4985        );
 4986    });
 4987}
 4988
 4989#[gpui::test]
 4990fn test_move_line_up_down(cx: &mut TestAppContext) {
 4991    init_test(cx, |_| {});
 4992
 4993    let editor = cx.add_window(|window, cx| {
 4994        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4995        build_editor(buffer, window, cx)
 4996    });
 4997    _ = editor.update(cx, |editor, window, cx| {
 4998        editor.fold_creases(
 4999            vec![
 5000                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5001                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5002                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5003            ],
 5004            true,
 5005            window,
 5006            cx,
 5007        );
 5008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5009            s.select_display_ranges([
 5010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5011                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5012                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5013                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5014            ])
 5015        });
 5016        assert_eq!(
 5017            editor.display_text(cx),
 5018            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5019        );
 5020
 5021        editor.move_line_up(&MoveLineUp, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5030                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5031                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5032                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_down(&MoveLineDown, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5064                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5065                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5066                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5067            ]
 5068        );
 5069    });
 5070
 5071    _ = editor.update(cx, |editor, window, cx| {
 5072        editor.move_line_up(&MoveLineUp, window, cx);
 5073        assert_eq!(
 5074            editor.display_text(cx),
 5075            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5076        );
 5077        assert_eq!(
 5078            editor.selections.display_ranges(cx),
 5079            vec![
 5080                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5081                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5082                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5083                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5084            ]
 5085        );
 5086    });
 5087}
 5088
 5089#[gpui::test]
 5090fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5091    init_test(cx, |_| {});
 5092    let editor = cx.add_window(|window, cx| {
 5093        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5094        build_editor(buffer, window, cx)
 5095    });
 5096    _ = editor.update(cx, |editor, window, cx| {
 5097        editor.fold_creases(
 5098            vec![Crease::simple(
 5099                Point::new(6, 4)..Point::new(7, 4),
 5100                FoldPlaceholder::test(),
 5101            )],
 5102            true,
 5103            window,
 5104            cx,
 5105        );
 5106        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5107            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5108        });
 5109        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5110        editor.move_line_up(&MoveLineUp, window, cx);
 5111        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5112        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5113    });
 5114}
 5115
 5116#[gpui::test]
 5117fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5118    init_test(cx, |_| {});
 5119
 5120    let editor = cx.add_window(|window, cx| {
 5121        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5122        build_editor(buffer, window, cx)
 5123    });
 5124    _ = editor.update(cx, |editor, window, cx| {
 5125        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5126        editor.insert_blocks(
 5127            [BlockProperties {
 5128                style: BlockStyle::Fixed,
 5129                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5130                height: Some(1),
 5131                render: Arc::new(|_| div().into_any()),
 5132                priority: 0,
 5133            }],
 5134            Some(Autoscroll::fit()),
 5135            cx,
 5136        );
 5137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5138            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5139        });
 5140        editor.move_line_down(&MoveLineDown, window, cx);
 5141    });
 5142}
 5143
 5144#[gpui::test]
 5145async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5146    init_test(cx, |_| {});
 5147
 5148    let mut cx = EditorTestContext::new(cx).await;
 5149    cx.set_state(
 5150        &"
 5151            ˇzero
 5152            one
 5153            two
 5154            three
 5155            four
 5156            five
 5157        "
 5158        .unindent(),
 5159    );
 5160
 5161    // Create a four-line block that replaces three lines of text.
 5162    cx.update_editor(|editor, window, cx| {
 5163        let snapshot = editor.snapshot(window, cx);
 5164        let snapshot = &snapshot.buffer_snapshot;
 5165        let placement = BlockPlacement::Replace(
 5166            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5167        );
 5168        editor.insert_blocks(
 5169            [BlockProperties {
 5170                placement,
 5171                height: Some(4),
 5172                style: BlockStyle::Sticky,
 5173                render: Arc::new(|_| gpui::div().into_any_element()),
 5174                priority: 0,
 5175            }],
 5176            None,
 5177            cx,
 5178        );
 5179    });
 5180
 5181    // Move down so that the cursor touches the block.
 5182    cx.update_editor(|editor, window, cx| {
 5183        editor.move_down(&Default::default(), window, cx);
 5184    });
 5185    cx.assert_editor_state(
 5186        &"
 5187            zero
 5188            «one
 5189            two
 5190            threeˇ»
 5191            four
 5192            five
 5193        "
 5194        .unindent(),
 5195    );
 5196
 5197    // Move down past the block.
 5198    cx.update_editor(|editor, window, cx| {
 5199        editor.move_down(&Default::default(), window, cx);
 5200    });
 5201    cx.assert_editor_state(
 5202        &"
 5203            zero
 5204            one
 5205            two
 5206            three
 5207            ˇfour
 5208            five
 5209        "
 5210        .unindent(),
 5211    );
 5212}
 5213
 5214#[gpui::test]
 5215fn test_transpose(cx: &mut TestAppContext) {
 5216    init_test(cx, |_| {});
 5217
 5218    _ = cx.add_window(|window, cx| {
 5219        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5220        editor.set_style(EditorStyle::default(), window, cx);
 5221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5222            s.select_ranges([1..1])
 5223        });
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "bac");
 5226        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5227
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "bca");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5231
 5232        editor.transpose(&Default::default(), window, cx);
 5233        assert_eq!(editor.text(cx), "bac");
 5234        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5235
 5236        editor
 5237    });
 5238
 5239    _ = cx.add_window(|window, cx| {
 5240        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5241        editor.set_style(EditorStyle::default(), window, cx);
 5242        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5243            s.select_ranges([3..3])
 5244        });
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "acb\nde");
 5247        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5248
 5249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5250            s.select_ranges([4..4])
 5251        });
 5252        editor.transpose(&Default::default(), window, cx);
 5253        assert_eq!(editor.text(cx), "acbd\ne");
 5254        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5255
 5256        editor.transpose(&Default::default(), window, cx);
 5257        assert_eq!(editor.text(cx), "acbde\n");
 5258        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5259
 5260        editor.transpose(&Default::default(), window, cx);
 5261        assert_eq!(editor.text(cx), "acbd\ne");
 5262        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5263
 5264        editor
 5265    });
 5266
 5267    _ = cx.add_window(|window, cx| {
 5268        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5269        editor.set_style(EditorStyle::default(), window, cx);
 5270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5271            s.select_ranges([1..1, 2..2, 4..4])
 5272        });
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bacd\ne");
 5275        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bcade\n");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5280
 5281        editor.transpose(&Default::default(), window, cx);
 5282        assert_eq!(editor.text(cx), "bcda\ne");
 5283        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5284
 5285        editor.transpose(&Default::default(), window, cx);
 5286        assert_eq!(editor.text(cx), "bcade\n");
 5287        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5288
 5289        editor.transpose(&Default::default(), window, cx);
 5290        assert_eq!(editor.text(cx), "bcaed\n");
 5291        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5292
 5293        editor
 5294    });
 5295
 5296    _ = cx.add_window(|window, cx| {
 5297        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5298        editor.set_style(EditorStyle::default(), window, cx);
 5299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5300            s.select_ranges([4..4])
 5301        });
 5302        editor.transpose(&Default::default(), window, cx);
 5303        assert_eq!(editor.text(cx), "🏀🍐✋");
 5304        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5305
 5306        editor.transpose(&Default::default(), window, cx);
 5307        assert_eq!(editor.text(cx), "🏀✋🍐");
 5308        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5309
 5310        editor.transpose(&Default::default(), window, cx);
 5311        assert_eq!(editor.text(cx), "🏀🍐✋");
 5312        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5313
 5314        editor
 5315    });
 5316}
 5317
 5318#[gpui::test]
 5319async fn test_rewrap(cx: &mut TestAppContext) {
 5320    init_test(cx, |settings| {
 5321        settings.languages.0.extend([
 5322            (
 5323                "Markdown".into(),
 5324                LanguageSettingsContent {
 5325                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5326                    preferred_line_length: Some(40),
 5327                    ..Default::default()
 5328                },
 5329            ),
 5330            (
 5331                "Plain Text".into(),
 5332                LanguageSettingsContent {
 5333                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5334                    preferred_line_length: Some(40),
 5335                    ..Default::default()
 5336                },
 5337            ),
 5338            (
 5339                "C++".into(),
 5340                LanguageSettingsContent {
 5341                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5342                    preferred_line_length: Some(40),
 5343                    ..Default::default()
 5344                },
 5345            ),
 5346            (
 5347                "Python".into(),
 5348                LanguageSettingsContent {
 5349                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5350                    preferred_line_length: Some(40),
 5351                    ..Default::default()
 5352                },
 5353            ),
 5354            (
 5355                "Rust".into(),
 5356                LanguageSettingsContent {
 5357                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5358                    preferred_line_length: Some(40),
 5359                    ..Default::default()
 5360                },
 5361            ),
 5362        ])
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    let cpp_language = Arc::new(Language::new(
 5368        LanguageConfig {
 5369            name: "C++".into(),
 5370            line_comments: vec!["// ".into()],
 5371            ..LanguageConfig::default()
 5372        },
 5373        None,
 5374    ));
 5375    let python_language = Arc::new(Language::new(
 5376        LanguageConfig {
 5377            name: "Python".into(),
 5378            line_comments: vec!["# ".into()],
 5379            ..LanguageConfig::default()
 5380        },
 5381        None,
 5382    ));
 5383    let markdown_language = Arc::new(Language::new(
 5384        LanguageConfig {
 5385            name: "Markdown".into(),
 5386            rewrap_prefixes: vec![
 5387                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5388                regex::Regex::new("[-*+]\\s+").unwrap(),
 5389            ],
 5390            ..LanguageConfig::default()
 5391        },
 5392        None,
 5393    ));
 5394    let rust_language = Arc::new(Language::new(
 5395        LanguageConfig {
 5396            name: "Rust".into(),
 5397            line_comments: vec!["// ".into(), "/// ".into()],
 5398            ..LanguageConfig::default()
 5399        },
 5400        Some(tree_sitter_rust::LANGUAGE.into()),
 5401    ));
 5402
 5403    let plaintext_language = Arc::new(Language::new(
 5404        LanguageConfig {
 5405            name: "Plain Text".into(),
 5406            ..LanguageConfig::default()
 5407        },
 5408        None,
 5409    ));
 5410
 5411    // Test basic rewrapping of a long line with a cursor
 5412    assert_rewrap(
 5413        indoc! {"
 5414            // ˇThis is a long comment that needs to be wrapped.
 5415        "},
 5416        indoc! {"
 5417            // ˇThis is a long comment that needs to
 5418            // be wrapped.
 5419        "},
 5420        cpp_language.clone(),
 5421        &mut cx,
 5422    );
 5423
 5424    // Test rewrapping a full selection
 5425    assert_rewrap(
 5426        indoc! {"
 5427            «// This selected long comment needs to be wrapped.ˇ»"
 5428        },
 5429        indoc! {"
 5430            «// This selected long comment needs to
 5431            // be wrapped.ˇ»"
 5432        },
 5433        cpp_language.clone(),
 5434        &mut cx,
 5435    );
 5436
 5437    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5438    assert_rewrap(
 5439        indoc! {"
 5440            // ˇThis is the first line.
 5441            // Thisˇ is the second line.
 5442            // This is the thirdˇ line, all part of one paragraph.
 5443         "},
 5444        indoc! {"
 5445            // ˇThis is the first line. Thisˇ is the
 5446            // second line. This is the thirdˇ line,
 5447            // all part of one paragraph.
 5448         "},
 5449        cpp_language.clone(),
 5450        &mut cx,
 5451    );
 5452
 5453    // Test multiple cursors in different paragraphs trigger separate rewraps
 5454    assert_rewrap(
 5455        indoc! {"
 5456            // ˇThis is the first paragraph, first line.
 5457            // ˇThis is the first paragraph, second line.
 5458
 5459            // ˇThis is the second paragraph, first line.
 5460            // ˇThis is the second paragraph, second line.
 5461        "},
 5462        indoc! {"
 5463            // ˇThis is the first paragraph, first
 5464            // line. ˇThis is the first paragraph,
 5465            // second line.
 5466
 5467            // ˇThis is the second paragraph, first
 5468            // line. ˇThis is the second paragraph,
 5469            // second line.
 5470        "},
 5471        cpp_language.clone(),
 5472        &mut cx,
 5473    );
 5474
 5475    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5476    assert_rewrap(
 5477        indoc! {"
 5478            «// A regular long long comment to be wrapped.
 5479            /// A documentation long comment to be wrapped.ˇ»
 5480          "},
 5481        indoc! {"
 5482            «// A regular long long comment to be
 5483            // wrapped.
 5484            /// A documentation long comment to be
 5485            /// wrapped.ˇ»
 5486          "},
 5487        rust_language.clone(),
 5488        &mut cx,
 5489    );
 5490
 5491    // Test that change in indentation level trigger seperate rewraps
 5492    assert_rewrap(
 5493        indoc! {"
 5494            fn foo() {
 5495                «// This is a long comment at the base indent.
 5496                    // This is a long comment at the next indent.ˇ»
 5497            }
 5498        "},
 5499        indoc! {"
 5500            fn foo() {
 5501                «// This is a long comment at the
 5502                // base indent.
 5503                    // This is a long comment at the
 5504                    // next indent.ˇ»
 5505            }
 5506        "},
 5507        rust_language.clone(),
 5508        &mut cx,
 5509    );
 5510
 5511    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5512    assert_rewrap(
 5513        indoc! {"
 5514            # ˇThis is a long comment using a pound sign.
 5515        "},
 5516        indoc! {"
 5517            # ˇThis is a long comment using a pound
 5518            # sign.
 5519        "},
 5520        python_language.clone(),
 5521        &mut cx,
 5522    );
 5523
 5524    // Test rewrapping only affects comments, not code even when selected
 5525    assert_rewrap(
 5526        indoc! {"
 5527            «/// This doc comment is long and should be wrapped.
 5528            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5529        "},
 5530        indoc! {"
 5531            «/// This doc comment is long and should
 5532            /// be wrapped.
 5533            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5534        "},
 5535        rust_language.clone(),
 5536        &mut cx,
 5537    );
 5538
 5539    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5540    assert_rewrap(
 5541        indoc! {"
 5542            # Header
 5543
 5544            A long long long line of markdown text to wrap.ˇ
 5545         "},
 5546        indoc! {"
 5547            # Header
 5548
 5549            A long long long line of markdown text
 5550            to wrap.ˇ
 5551         "},
 5552        markdown_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5557    assert_rewrap(
 5558        indoc! {"
 5559            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5560            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5561            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5562        "},
 5563        indoc! {"
 5564            «1. This is a numbered list item that is
 5565               very long and needs to be wrapped
 5566               properly.
 5567            2. This is a numbered list item that is
 5568               very long and needs to be wrapped
 5569               properly.
 5570            - This is an unordered list item that is
 5571              also very long and should not merge
 5572              with the numbered item.ˇ»
 5573        "},
 5574        markdown_language.clone(),
 5575        &mut cx,
 5576    );
 5577
 5578    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5579    assert_rewrap(
 5580        indoc! {"
 5581            «1. This is a numbered list item that is
 5582            very long and needs to be wrapped
 5583            properly.
 5584            2. This is a numbered list item that is
 5585            very long and needs to be wrapped
 5586            properly.
 5587            - This is an unordered list item that is
 5588            also very long and should not merge with
 5589            the numbered item.ˇ»
 5590        "},
 5591        indoc! {"
 5592            «1. This is a numbered list item that is
 5593               very long and needs to be wrapped
 5594               properly.
 5595            2. This is a numbered list item that is
 5596               very long and needs to be wrapped
 5597               properly.
 5598            - This is an unordered list item that is
 5599              also very long and should not merge
 5600              with the numbered item.ˇ»
 5601        "},
 5602        markdown_language.clone(),
 5603        &mut cx,
 5604    );
 5605
 5606    // Test that rewrapping maintain indents even when they already exists.
 5607    assert_rewrap(
 5608        indoc! {"
 5609            «1. This is a numbered list
 5610               item that is very long and needs to be wrapped properly.
 5611            2. This is a numbered list
 5612               item that is very long and needs to be wrapped properly.
 5613            - This is an unordered list item that is also very long and
 5614              should not merge with the numbered item.ˇ»
 5615        "},
 5616        indoc! {"
 5617            «1. This is a numbered list item that is
 5618               very long and needs to be wrapped
 5619               properly.
 5620            2. This is a numbered list item that is
 5621               very long and needs to be wrapped
 5622               properly.
 5623            - This is an unordered list item that is
 5624              also very long and should not merge
 5625              with the numbered item.ˇ»
 5626        "},
 5627        markdown_language.clone(),
 5628        &mut cx,
 5629    );
 5630
 5631    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5632    assert_rewrap(
 5633        indoc! {"
 5634            ˇThis is a very long line of plain text that will be wrapped.
 5635        "},
 5636        indoc! {"
 5637            ˇThis is a very long line of plain text
 5638            that will be wrapped.
 5639        "},
 5640        plaintext_language.clone(),
 5641        &mut cx,
 5642    );
 5643
 5644    // Test that non-commented code acts as a paragraph boundary within a selection
 5645    assert_rewrap(
 5646        indoc! {"
 5647               «// This is the first long comment block to be wrapped.
 5648               fn my_func(a: u32);
 5649               // This is the second long comment block to be wrapped.ˇ»
 5650           "},
 5651        indoc! {"
 5652               «// This is the first long comment block
 5653               // to be wrapped.
 5654               fn my_func(a: u32);
 5655               // This is the second long comment block
 5656               // to be wrapped.ˇ»
 5657           "},
 5658        rust_language.clone(),
 5659        &mut cx,
 5660    );
 5661
 5662    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5663    assert_rewrap(
 5664        indoc! {"
 5665            «ˇThis is a very long line that will be wrapped.
 5666
 5667            This is another paragraph in the same selection.»
 5668
 5669            «\tThis is a very long indented line that will be wrapped.ˇ»
 5670         "},
 5671        indoc! {"
 5672            «ˇThis is a very long line that will be
 5673            wrapped.
 5674
 5675            This is another paragraph in the same
 5676            selection.»
 5677
 5678            «\tThis is a very long indented line
 5679            \tthat will be wrapped.ˇ»
 5680         "},
 5681        plaintext_language.clone(),
 5682        &mut cx,
 5683    );
 5684
 5685    // Test that an empty comment line acts as a paragraph boundary
 5686    assert_rewrap(
 5687        indoc! {"
 5688            // ˇThis is a long comment that will be wrapped.
 5689            //
 5690            // And this is another long comment that will also be wrapped.ˇ
 5691         "},
 5692        indoc! {"
 5693            // ˇThis is a long comment that will be
 5694            // wrapped.
 5695            //
 5696            // And this is another long comment that
 5697            // will also be wrapped.ˇ
 5698         "},
 5699        cpp_language,
 5700        &mut cx,
 5701    );
 5702
 5703    #[track_caller]
 5704    fn assert_rewrap(
 5705        unwrapped_text: &str,
 5706        wrapped_text: &str,
 5707        language: Arc<Language>,
 5708        cx: &mut EditorTestContext,
 5709    ) {
 5710        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5711        cx.set_state(unwrapped_text);
 5712        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5713        cx.assert_editor_state(wrapped_text);
 5714    }
 5715}
 5716
 5717#[gpui::test]
 5718async fn test_hard_wrap(cx: &mut TestAppContext) {
 5719    init_test(cx, |_| {});
 5720    let mut cx = EditorTestContext::new(cx).await;
 5721
 5722    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5723    cx.update_editor(|editor, _, cx| {
 5724        editor.set_hard_wrap(Some(14), cx);
 5725    });
 5726
 5727    cx.set_state(indoc!(
 5728        "
 5729        one two three ˇ
 5730        "
 5731    ));
 5732    cx.simulate_input("four");
 5733    cx.run_until_parked();
 5734
 5735    cx.assert_editor_state(indoc!(
 5736        "
 5737        one two three
 5738        fourˇ
 5739        "
 5740    ));
 5741
 5742    cx.update_editor(|editor, window, cx| {
 5743        editor.newline(&Default::default(), window, cx);
 5744    });
 5745    cx.run_until_parked();
 5746    cx.assert_editor_state(indoc!(
 5747        "
 5748        one two three
 5749        four
 5750        ˇ
 5751        "
 5752    ));
 5753
 5754    cx.simulate_input("five");
 5755    cx.run_until_parked();
 5756    cx.assert_editor_state(indoc!(
 5757        "
 5758        one two three
 5759        four
 5760        fiveˇ
 5761        "
 5762    ));
 5763
 5764    cx.update_editor(|editor, window, cx| {
 5765        editor.newline(&Default::default(), window, cx);
 5766    });
 5767    cx.run_until_parked();
 5768    cx.simulate_input("# ");
 5769    cx.run_until_parked();
 5770    cx.assert_editor_state(indoc!(
 5771        "
 5772        one two three
 5773        four
 5774        five
 5775        # ˇ
 5776        "
 5777    ));
 5778
 5779    cx.update_editor(|editor, window, cx| {
 5780        editor.newline(&Default::default(), window, cx);
 5781    });
 5782    cx.run_until_parked();
 5783    cx.assert_editor_state(indoc!(
 5784        "
 5785        one two three
 5786        four
 5787        five
 5788        #\x20
 5789 5790        "
 5791    ));
 5792
 5793    cx.simulate_input(" 6");
 5794    cx.run_until_parked();
 5795    cx.assert_editor_state(indoc!(
 5796        "
 5797        one two three
 5798        four
 5799        five
 5800        #
 5801        # 6ˇ
 5802        "
 5803    ));
 5804}
 5805
 5806#[gpui::test]
 5807async fn test_clipboard(cx: &mut TestAppContext) {
 5808    init_test(cx, |_| {});
 5809
 5810    let mut cx = EditorTestContext::new(cx).await;
 5811
 5812    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5813    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5814    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5815
 5816    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5817    cx.set_state("two ˇfour ˇsix ˇ");
 5818    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5819    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5820
 5821    // Paste again but with only two cursors. Since the number of cursors doesn't
 5822    // match the number of slices in the clipboard, the entire clipboard text
 5823    // is pasted at each cursor.
 5824    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5825    cx.update_editor(|e, window, cx| {
 5826        e.handle_input("( ", window, cx);
 5827        e.paste(&Paste, window, cx);
 5828        e.handle_input(") ", window, cx);
 5829    });
 5830    cx.assert_editor_state(
 5831        &([
 5832            "( one✅ ",
 5833            "three ",
 5834            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5835            "three ",
 5836            "five ) ˇ",
 5837        ]
 5838        .join("\n")),
 5839    );
 5840
 5841    // Cut with three selections, one of which is full-line.
 5842    cx.set_state(indoc! {"
 5843        1«2ˇ»3
 5844        4ˇ567
 5845        «8ˇ»9"});
 5846    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5847    cx.assert_editor_state(indoc! {"
 5848        1ˇ3
 5849        ˇ9"});
 5850
 5851    // Paste with three selections, noticing how the copied selection that was full-line
 5852    // gets inserted before the second cursor.
 5853    cx.set_state(indoc! {"
 5854        1ˇ3
 5855 5856        «oˇ»ne"});
 5857    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5858    cx.assert_editor_state(indoc! {"
 5859        12ˇ3
 5860        4567
 5861 5862        8ˇne"});
 5863
 5864    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5865    cx.set_state(indoc! {"
 5866        The quick brown
 5867        fox juˇmps over
 5868        the lazy dog"});
 5869    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5870    assert_eq!(
 5871        cx.read_from_clipboard()
 5872            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5873        Some("fox jumps over\n".to_string())
 5874    );
 5875
 5876    // Paste with three selections, noticing how the copied full-line selection is inserted
 5877    // before the empty selections but replaces the selection that is non-empty.
 5878    cx.set_state(indoc! {"
 5879        Tˇhe quick brown
 5880        «foˇ»x jumps over
 5881        tˇhe lazy dog"});
 5882    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5883    cx.assert_editor_state(indoc! {"
 5884        fox jumps over
 5885        Tˇhe quick brown
 5886        fox jumps over
 5887        ˇx jumps over
 5888        fox jumps over
 5889        tˇhe lazy dog"});
 5890}
 5891
 5892#[gpui::test]
 5893async fn test_copy_trim(cx: &mut TestAppContext) {
 5894    init_test(cx, |_| {});
 5895
 5896    let mut cx = EditorTestContext::new(cx).await;
 5897    cx.set_state(
 5898        r#"            «for selection in selections.iter() {
 5899            let mut start = selection.start;
 5900            let mut end = selection.end;
 5901            let is_entire_line = selection.is_empty();
 5902            if is_entire_line {
 5903                start = Point::new(start.row, 0);ˇ»
 5904                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5905            }
 5906        "#,
 5907    );
 5908    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5909    assert_eq!(
 5910        cx.read_from_clipboard()
 5911            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5912        Some(
 5913            "for selection in selections.iter() {
 5914            let mut start = selection.start;
 5915            let mut end = selection.end;
 5916            let is_entire_line = selection.is_empty();
 5917            if is_entire_line {
 5918                start = Point::new(start.row, 0);"
 5919                .to_string()
 5920        ),
 5921        "Regular copying preserves all indentation selected",
 5922    );
 5923    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5924    assert_eq!(
 5925        cx.read_from_clipboard()
 5926            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5927        Some(
 5928            "for selection in selections.iter() {
 5929let mut start = selection.start;
 5930let mut end = selection.end;
 5931let is_entire_line = selection.is_empty();
 5932if is_entire_line {
 5933    start = Point::new(start.row, 0);"
 5934                .to_string()
 5935        ),
 5936        "Copying with stripping should strip all leading whitespaces"
 5937    );
 5938
 5939    cx.set_state(
 5940        r#"       «     for selection in selections.iter() {
 5941            let mut start = selection.start;
 5942            let mut end = selection.end;
 5943            let is_entire_line = selection.is_empty();
 5944            if is_entire_line {
 5945                start = Point::new(start.row, 0);ˇ»
 5946                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5947            }
 5948        "#,
 5949    );
 5950    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5951    assert_eq!(
 5952        cx.read_from_clipboard()
 5953            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5954        Some(
 5955            "     for selection in selections.iter() {
 5956            let mut start = selection.start;
 5957            let mut end = selection.end;
 5958            let is_entire_line = selection.is_empty();
 5959            if is_entire_line {
 5960                start = Point::new(start.row, 0);"
 5961                .to_string()
 5962        ),
 5963        "Regular copying preserves all indentation selected",
 5964    );
 5965    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5966    assert_eq!(
 5967        cx.read_from_clipboard()
 5968            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5969        Some(
 5970            "for selection in selections.iter() {
 5971let mut start = selection.start;
 5972let mut end = selection.end;
 5973let is_entire_line = selection.is_empty();
 5974if is_entire_line {
 5975    start = Point::new(start.row, 0);"
 5976                .to_string()
 5977        ),
 5978        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5979    );
 5980
 5981    cx.set_state(
 5982        r#"       «ˇ     for selection in selections.iter() {
 5983            let mut start = selection.start;
 5984            let mut end = selection.end;
 5985            let is_entire_line = selection.is_empty();
 5986            if is_entire_line {
 5987                start = Point::new(start.row, 0);»
 5988                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5989            }
 5990        "#,
 5991    );
 5992    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5993    assert_eq!(
 5994        cx.read_from_clipboard()
 5995            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5996        Some(
 5997            "     for selection in selections.iter() {
 5998            let mut start = selection.start;
 5999            let mut end = selection.end;
 6000            let is_entire_line = selection.is_empty();
 6001            if is_entire_line {
 6002                start = Point::new(start.row, 0);"
 6003                .to_string()
 6004        ),
 6005        "Regular copying for reverse selection works the same",
 6006    );
 6007    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6008    assert_eq!(
 6009        cx.read_from_clipboard()
 6010            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6011        Some(
 6012            "for selection in selections.iter() {
 6013let mut start = selection.start;
 6014let mut end = selection.end;
 6015let is_entire_line = selection.is_empty();
 6016if is_entire_line {
 6017    start = Point::new(start.row, 0);"
 6018                .to_string()
 6019        ),
 6020        "Copying with stripping for reverse selection works the same"
 6021    );
 6022
 6023    cx.set_state(
 6024        r#"            for selection «in selections.iter() {
 6025            let mut start = selection.start;
 6026            let mut end = selection.end;
 6027            let is_entire_line = selection.is_empty();
 6028            if is_entire_line {
 6029                start = Point::new(start.row, 0);ˇ»
 6030                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6031            }
 6032        "#,
 6033    );
 6034    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6035    assert_eq!(
 6036        cx.read_from_clipboard()
 6037            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6038        Some(
 6039            "in selections.iter() {
 6040            let mut start = selection.start;
 6041            let mut end = selection.end;
 6042            let is_entire_line = selection.is_empty();
 6043            if is_entire_line {
 6044                start = Point::new(start.row, 0);"
 6045                .to_string()
 6046        ),
 6047        "When selecting past the indent, the copying works as usual",
 6048    );
 6049    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6050    assert_eq!(
 6051        cx.read_from_clipboard()
 6052            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6053        Some(
 6054            "in selections.iter() {
 6055            let mut start = selection.start;
 6056            let mut end = selection.end;
 6057            let is_entire_line = selection.is_empty();
 6058            if is_entire_line {
 6059                start = Point::new(start.row, 0);"
 6060                .to_string()
 6061        ),
 6062        "When selecting past the indent, nothing is trimmed"
 6063    );
 6064
 6065    cx.set_state(
 6066        r#"            «for selection in selections.iter() {
 6067            let mut start = selection.start;
 6068
 6069            let mut end = selection.end;
 6070            let is_entire_line = selection.is_empty();
 6071            if is_entire_line {
 6072                start = Point::new(start.row, 0);
 6073ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6074            }
 6075        "#,
 6076    );
 6077    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6078    assert_eq!(
 6079        cx.read_from_clipboard()
 6080            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6081        Some(
 6082            "for selection in selections.iter() {
 6083let mut start = selection.start;
 6084
 6085let mut end = selection.end;
 6086let is_entire_line = selection.is_empty();
 6087if is_entire_line {
 6088    start = Point::new(start.row, 0);
 6089"
 6090            .to_string()
 6091        ),
 6092        "Copying with stripping should ignore empty lines"
 6093    );
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_paste_multiline(cx: &mut TestAppContext) {
 6098    init_test(cx, |_| {});
 6099
 6100    let mut cx = EditorTestContext::new(cx).await;
 6101    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6102
 6103    // Cut an indented block, without the leading whitespace.
 6104    cx.set_state(indoc! {"
 6105        const a: B = (
 6106            c(),
 6107            «d(
 6108                e,
 6109                f
 6110            )ˇ»
 6111        );
 6112    "});
 6113    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6114    cx.assert_editor_state(indoc! {"
 6115        const a: B = (
 6116            c(),
 6117            ˇ
 6118        );
 6119    "});
 6120
 6121    // Paste it at the same position.
 6122    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6123    cx.assert_editor_state(indoc! {"
 6124        const a: B = (
 6125            c(),
 6126            d(
 6127                e,
 6128                f
 6129 6130        );
 6131    "});
 6132
 6133    // Paste it at a line with a lower indent level.
 6134    cx.set_state(indoc! {"
 6135        ˇ
 6136        const a: B = (
 6137            c(),
 6138        );
 6139    "});
 6140    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6141    cx.assert_editor_state(indoc! {"
 6142        d(
 6143            e,
 6144            f
 6145 6146        const a: B = (
 6147            c(),
 6148        );
 6149    "});
 6150
 6151    // Cut an indented block, with the leading whitespace.
 6152    cx.set_state(indoc! {"
 6153        const a: B = (
 6154            c(),
 6155        «    d(
 6156                e,
 6157                f
 6158            )
 6159        ˇ»);
 6160    "});
 6161    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6162    cx.assert_editor_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165        ˇ);
 6166    "});
 6167
 6168    // Paste it at the same position.
 6169    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6170    cx.assert_editor_state(indoc! {"
 6171        const a: B = (
 6172            c(),
 6173            d(
 6174                e,
 6175                f
 6176            )
 6177        ˇ);
 6178    "});
 6179
 6180    // Paste it at a line with a higher indent level.
 6181    cx.set_state(indoc! {"
 6182        const a: B = (
 6183            c(),
 6184            d(
 6185                e,
 6186 6187            )
 6188        );
 6189    "});
 6190    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6191    cx.assert_editor_state(indoc! {"
 6192        const a: B = (
 6193            c(),
 6194            d(
 6195                e,
 6196                f    d(
 6197                    e,
 6198                    f
 6199                )
 6200        ˇ
 6201            )
 6202        );
 6203    "});
 6204
 6205    // Copy an indented block, starting mid-line
 6206    cx.set_state(indoc! {"
 6207        const a: B = (
 6208            c(),
 6209            somethin«g(
 6210                e,
 6211                f
 6212            )ˇ»
 6213        );
 6214    "});
 6215    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6216
 6217    // Paste it on a line with a lower indent level
 6218    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6219    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6220    cx.assert_editor_state(indoc! {"
 6221        const a: B = (
 6222            c(),
 6223            something(
 6224                e,
 6225                f
 6226            )
 6227        );
 6228        g(
 6229            e,
 6230            f
 6231"});
 6232}
 6233
 6234#[gpui::test]
 6235async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6236    init_test(cx, |_| {});
 6237
 6238    cx.write_to_clipboard(ClipboardItem::new_string(
 6239        "    d(\n        e\n    );\n".into(),
 6240    ));
 6241
 6242    let mut cx = EditorTestContext::new(cx).await;
 6243    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6244
 6245    cx.set_state(indoc! {"
 6246        fn a() {
 6247            b();
 6248            if c() {
 6249                ˇ
 6250            }
 6251        }
 6252    "});
 6253
 6254    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6255    cx.assert_editor_state(indoc! {"
 6256        fn a() {
 6257            b();
 6258            if c() {
 6259                d(
 6260                    e
 6261                );
 6262        ˇ
 6263            }
 6264        }
 6265    "});
 6266
 6267    cx.set_state(indoc! {"
 6268        fn a() {
 6269            b();
 6270            ˇ
 6271        }
 6272    "});
 6273
 6274    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6275    cx.assert_editor_state(indoc! {"
 6276        fn a() {
 6277            b();
 6278            d(
 6279                e
 6280            );
 6281        ˇ
 6282        }
 6283    "});
 6284}
 6285
 6286#[gpui::test]
 6287fn test_select_all(cx: &mut TestAppContext) {
 6288    init_test(cx, |_| {});
 6289
 6290    let editor = cx.add_window(|window, cx| {
 6291        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6292        build_editor(buffer, window, cx)
 6293    });
 6294    _ = editor.update(cx, |editor, window, cx| {
 6295        editor.select_all(&SelectAll, window, cx);
 6296        assert_eq!(
 6297            editor.selections.display_ranges(cx),
 6298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6299        );
 6300    });
 6301}
 6302
 6303#[gpui::test]
 6304fn test_select_line(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306
 6307    let editor = cx.add_window(|window, cx| {
 6308        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6309        build_editor(buffer, window, cx)
 6310    });
 6311    _ = editor.update(cx, |editor, window, cx| {
 6312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6313            s.select_display_ranges([
 6314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6315                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6316                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6317                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6318            ])
 6319        });
 6320        editor.select_line(&SelectLine, window, cx);
 6321        assert_eq!(
 6322            editor.selections.display_ranges(cx),
 6323            vec![
 6324                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6325                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6326            ]
 6327        );
 6328    });
 6329
 6330    _ = editor.update(cx, |editor, window, cx| {
 6331        editor.select_line(&SelectLine, window, cx);
 6332        assert_eq!(
 6333            editor.selections.display_ranges(cx),
 6334            vec![
 6335                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6336                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6337            ]
 6338        );
 6339    });
 6340
 6341    _ = editor.update(cx, |editor, window, cx| {
 6342        editor.select_line(&SelectLine, window, cx);
 6343        assert_eq!(
 6344            editor.selections.display_ranges(cx),
 6345            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6346        );
 6347    });
 6348}
 6349
 6350#[gpui::test]
 6351async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6352    init_test(cx, |_| {});
 6353    let mut cx = EditorTestContext::new(cx).await;
 6354
 6355    #[track_caller]
 6356    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6357        cx.set_state(initial_state);
 6358        cx.update_editor(|e, window, cx| {
 6359            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6360        });
 6361        cx.assert_editor_state(expected_state);
 6362    }
 6363
 6364    // Selection starts and ends at the middle of lines, left-to-right
 6365    test(
 6366        &mut cx,
 6367        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6368        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6369    );
 6370    // Same thing, right-to-left
 6371    test(
 6372        &mut cx,
 6373        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6374        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6375    );
 6376
 6377    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6378    test(
 6379        &mut cx,
 6380        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6381        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6382    );
 6383    // Same thing, right-to-left
 6384    test(
 6385        &mut cx,
 6386        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6388    );
 6389
 6390    // Whole buffer, left-to-right, last line ends with newline
 6391    test(
 6392        &mut cx,
 6393        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6394        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6395    );
 6396    // Same thing, right-to-left
 6397    test(
 6398        &mut cx,
 6399        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6400        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6401    );
 6402
 6403    // Starts at the end of a line, ends at the start of another
 6404    test(
 6405        &mut cx,
 6406        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6407        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6408    );
 6409}
 6410
 6411#[gpui::test]
 6412async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6413    init_test(cx, |_| {});
 6414
 6415    let editor = cx.add_window(|window, cx| {
 6416        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6417        build_editor(buffer, window, cx)
 6418    });
 6419
 6420    // setup
 6421    _ = editor.update(cx, |editor, window, cx| {
 6422        editor.fold_creases(
 6423            vec![
 6424                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6425                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6426                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6427            ],
 6428            true,
 6429            window,
 6430            cx,
 6431        );
 6432        assert_eq!(
 6433            editor.display_text(cx),
 6434            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6435        );
 6436    });
 6437
 6438    _ = editor.update(cx, |editor, window, cx| {
 6439        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6440            s.select_display_ranges([
 6441                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6442                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6443                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6444                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6445            ])
 6446        });
 6447        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6448        assert_eq!(
 6449            editor.display_text(cx),
 6450            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6451        );
 6452    });
 6453    EditorTestContext::for_editor(editor, cx)
 6454        .await
 6455        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6456
 6457    _ = editor.update(cx, |editor, window, cx| {
 6458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6459            s.select_display_ranges([
 6460                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6461            ])
 6462        });
 6463        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6464        assert_eq!(
 6465            editor.display_text(cx),
 6466            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6467        );
 6468        assert_eq!(
 6469            editor.selections.display_ranges(cx),
 6470            [
 6471                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6472                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6473                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6474                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6475                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6476                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6477                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6478            ]
 6479        );
 6480    });
 6481    EditorTestContext::for_editor(editor, cx)
 6482        .await
 6483        .assert_editor_state(
 6484            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6485        );
 6486}
 6487
 6488#[gpui::test]
 6489async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6490    init_test(cx, |_| {});
 6491
 6492    let mut cx = EditorTestContext::new(cx).await;
 6493
 6494    cx.set_state(indoc!(
 6495        r#"abc
 6496           defˇghi
 6497
 6498           jk
 6499           nlmo
 6500           "#
 6501    ));
 6502
 6503    cx.update_editor(|editor, window, cx| {
 6504        editor.add_selection_above(&Default::default(), window, cx);
 6505    });
 6506
 6507    cx.assert_editor_state(indoc!(
 6508        r#"abcˇ
 6509           defˇghi
 6510
 6511           jk
 6512           nlmo
 6513           "#
 6514    ));
 6515
 6516    cx.update_editor(|editor, window, cx| {
 6517        editor.add_selection_above(&Default::default(), window, cx);
 6518    });
 6519
 6520    cx.assert_editor_state(indoc!(
 6521        r#"abcˇ
 6522            defˇghi
 6523
 6524            jk
 6525            nlmo
 6526            "#
 6527    ));
 6528
 6529    cx.update_editor(|editor, window, cx| {
 6530        editor.add_selection_below(&Default::default(), window, cx);
 6531    });
 6532
 6533    cx.assert_editor_state(indoc!(
 6534        r#"abc
 6535           defˇghi
 6536
 6537           jk
 6538           nlmo
 6539           "#
 6540    ));
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.undo_selection(&Default::default(), window, cx);
 6544    });
 6545
 6546    cx.assert_editor_state(indoc!(
 6547        r#"abcˇ
 6548           defˇghi
 6549
 6550           jk
 6551           nlmo
 6552           "#
 6553    ));
 6554
 6555    cx.update_editor(|editor, window, cx| {
 6556        editor.redo_selection(&Default::default(), window, cx);
 6557    });
 6558
 6559    cx.assert_editor_state(indoc!(
 6560        r#"abc
 6561           defˇghi
 6562
 6563           jk
 6564           nlmo
 6565           "#
 6566    ));
 6567
 6568    cx.update_editor(|editor, window, cx| {
 6569        editor.add_selection_below(&Default::default(), window, cx);
 6570    });
 6571
 6572    cx.assert_editor_state(indoc!(
 6573        r#"abc
 6574           defˇghi
 6575           ˇ
 6576           jk
 6577           nlmo
 6578           "#
 6579    ));
 6580
 6581    cx.update_editor(|editor, window, cx| {
 6582        editor.add_selection_below(&Default::default(), window, cx);
 6583    });
 6584
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abc
 6587           defˇghi
 6588           ˇ
 6589           jkˇ
 6590           nlmo
 6591           "#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.add_selection_below(&Default::default(), window, cx);
 6596    });
 6597
 6598    cx.assert_editor_state(indoc!(
 6599        r#"abc
 6600           defˇghi
 6601           ˇ
 6602           jkˇ
 6603           nlmˇo
 6604           "#
 6605    ));
 6606
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_below(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"abc
 6613           defˇghi
 6614           ˇ
 6615           jkˇ
 6616           nlmˇo
 6617           ˇ"#
 6618    ));
 6619
 6620    // change selections
 6621    cx.set_state(indoc!(
 6622        r#"abc
 6623           def«ˇg»hi
 6624
 6625           jk
 6626           nlmo
 6627           "#
 6628    ));
 6629
 6630    cx.update_editor(|editor, window, cx| {
 6631        editor.add_selection_below(&Default::default(), window, cx);
 6632    });
 6633
 6634    cx.assert_editor_state(indoc!(
 6635        r#"abc
 6636           def«ˇg»hi
 6637
 6638           jk
 6639           nlm«ˇo»
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"abc
 6649           def«ˇg»hi
 6650
 6651           jk
 6652           nlm«ˇo»
 6653           "#
 6654    ));
 6655
 6656    cx.update_editor(|editor, window, cx| {
 6657        editor.add_selection_above(&Default::default(), window, cx);
 6658    });
 6659
 6660    cx.assert_editor_state(indoc!(
 6661        r#"abc
 6662           def«ˇg»hi
 6663
 6664           jk
 6665           nlmo
 6666           "#
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.add_selection_above(&Default::default(), window, cx);
 6671    });
 6672
 6673    cx.assert_editor_state(indoc!(
 6674        r#"abc
 6675           def«ˇg»hi
 6676
 6677           jk
 6678           nlmo
 6679           "#
 6680    ));
 6681
 6682    // Change selections again
 6683    cx.set_state(indoc!(
 6684        r#"a«bc
 6685           defgˇ»hi
 6686
 6687           jk
 6688           nlmo
 6689           "#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_below(&Default::default(), window, cx);
 6694    });
 6695
 6696    cx.assert_editor_state(indoc!(
 6697        r#"a«bcˇ»
 6698           d«efgˇ»hi
 6699
 6700           j«kˇ»
 6701           nlmo
 6702           "#
 6703    ));
 6704
 6705    cx.update_editor(|editor, window, cx| {
 6706        editor.add_selection_below(&Default::default(), window, cx);
 6707    });
 6708    cx.assert_editor_state(indoc!(
 6709        r#"a«bcˇ»
 6710           d«efgˇ»hi
 6711
 6712           j«kˇ»
 6713           n«lmoˇ»
 6714           "#
 6715    ));
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_above(&Default::default(), window, cx);
 6718    });
 6719
 6720    cx.assert_editor_state(indoc!(
 6721        r#"a«bcˇ»
 6722           d«efgˇ»hi
 6723
 6724           j«kˇ»
 6725           nlmo
 6726           "#
 6727    ));
 6728
 6729    // Change selections again
 6730    cx.set_state(indoc!(
 6731        r#"abc
 6732           d«ˇefghi
 6733
 6734           jk
 6735           nlm»o
 6736           "#
 6737    ));
 6738
 6739    cx.update_editor(|editor, window, cx| {
 6740        editor.add_selection_above(&Default::default(), window, cx);
 6741    });
 6742
 6743    cx.assert_editor_state(indoc!(
 6744        r#"a«ˇbc»
 6745           d«ˇef»ghi
 6746
 6747           j«ˇk»
 6748           n«ˇlm»o
 6749           "#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.add_selection_below(&Default::default(), window, cx);
 6754    });
 6755
 6756    cx.assert_editor_state(indoc!(
 6757        r#"abc
 6758           d«ˇef»ghi
 6759
 6760           j«ˇk»
 6761           n«ˇlm»o
 6762           "#
 6763    ));
 6764}
 6765
 6766#[gpui::test]
 6767async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6768    init_test(cx, |_| {});
 6769    let mut cx = EditorTestContext::new(cx).await;
 6770
 6771    cx.set_state(indoc!(
 6772        r#"line onˇe
 6773           liˇne two
 6774           line three
 6775           line four"#
 6776    ));
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursors expand in the same direction
 6783    cx.assert_editor_state(indoc!(
 6784        r#"line onˇe
 6785           liˇne twˇo
 6786           liˇne three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.add_selection_below(&Default::default(), window, cx);
 6792    });
 6793
 6794    cx.update_editor(|editor, window, cx| {
 6795        editor.add_selection_below(&Default::default(), window, cx);
 6796    });
 6797
 6798    // test multiple cursors expand below overflow
 6799    cx.assert_editor_state(indoc!(
 6800        r#"line onˇe
 6801           liˇne twˇo
 6802           liˇne thˇree
 6803           liˇne foˇur"#
 6804    ));
 6805
 6806    cx.update_editor(|editor, window, cx| {
 6807        editor.add_selection_above(&Default::default(), window, cx);
 6808    });
 6809
 6810    // test multiple cursors retrieves back correctly
 6811    cx.assert_editor_state(indoc!(
 6812        r#"line onˇe
 6813           liˇne twˇo
 6814           liˇne thˇree
 6815           line four"#
 6816    ));
 6817
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.add_selection_above(&Default::default(), window, cx);
 6820    });
 6821
 6822    cx.update_editor(|editor, window, cx| {
 6823        editor.add_selection_above(&Default::default(), window, cx);
 6824    });
 6825
 6826    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6827    cx.assert_editor_state(indoc!(
 6828        r#"liˇne onˇe
 6829           liˇne two
 6830           line three
 6831           line four"#
 6832    ));
 6833
 6834    cx.update_editor(|editor, window, cx| {
 6835        editor.undo_selection(&Default::default(), window, cx);
 6836    });
 6837
 6838    // test undo
 6839    cx.assert_editor_state(indoc!(
 6840        r#"line onˇe
 6841           liˇne twˇo
 6842           line three
 6843           line four"#
 6844    ));
 6845
 6846    cx.update_editor(|editor, window, cx| {
 6847        editor.redo_selection(&Default::default(), window, cx);
 6848    });
 6849
 6850    // test redo
 6851    cx.assert_editor_state(indoc!(
 6852        r#"liˇne onˇe
 6853           liˇne two
 6854           line three
 6855           line four"#
 6856    ));
 6857
 6858    cx.set_state(indoc!(
 6859        r#"abcd
 6860           ef«ghˇ»
 6861           ijkl
 6862           «mˇ»nop"#
 6863    ));
 6864
 6865    cx.update_editor(|editor, window, cx| {
 6866        editor.add_selection_above(&Default::default(), window, cx);
 6867    });
 6868
 6869    // test multiple selections expand in the same direction
 6870    cx.assert_editor_state(indoc!(
 6871        r#"ab«cdˇ»
 6872           ef«ghˇ»
 6873           «iˇ»jkl
 6874           «mˇ»nop"#
 6875    ));
 6876
 6877    cx.update_editor(|editor, window, cx| {
 6878        editor.add_selection_above(&Default::default(), window, cx);
 6879    });
 6880
 6881    // test multiple selection upward overflow
 6882    cx.assert_editor_state(indoc!(
 6883        r#"ab«cdˇ»
 6884           «eˇ»f«ghˇ»
 6885           «iˇ»jkl
 6886           «mˇ»nop"#
 6887    ));
 6888
 6889    cx.update_editor(|editor, window, cx| {
 6890        editor.add_selection_below(&Default::default(), window, cx);
 6891    });
 6892
 6893    // test multiple selection retrieves back correctly
 6894    cx.assert_editor_state(indoc!(
 6895        r#"abcd
 6896           ef«ghˇ»
 6897           «iˇ»jkl
 6898           «mˇ»nop"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_below(&Default::default(), window, cx);
 6903    });
 6904
 6905    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6906    cx.assert_editor_state(indoc!(
 6907        r#"abcd
 6908           ef«ghˇ»
 6909           ij«klˇ»
 6910           «mˇ»nop"#
 6911    ));
 6912
 6913    cx.update_editor(|editor, window, cx| {
 6914        editor.undo_selection(&Default::default(), window, cx);
 6915    });
 6916
 6917    // test undo
 6918    cx.assert_editor_state(indoc!(
 6919        r#"abcd
 6920           ef«ghˇ»
 6921           «iˇ»jkl
 6922           «mˇ»nop"#
 6923    ));
 6924
 6925    cx.update_editor(|editor, window, cx| {
 6926        editor.redo_selection(&Default::default(), window, cx);
 6927    });
 6928
 6929    // test redo
 6930    cx.assert_editor_state(indoc!(
 6931        r#"abcd
 6932           ef«ghˇ»
 6933           ij«klˇ»
 6934           «mˇ»nop"#
 6935    ));
 6936}
 6937
 6938#[gpui::test]
 6939async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6940    init_test(cx, |_| {});
 6941    let mut cx = EditorTestContext::new(cx).await;
 6942
 6943    cx.set_state(indoc!(
 6944        r#"line onˇe
 6945           liˇne two
 6946           line three
 6947           line four"#
 6948    ));
 6949
 6950    cx.update_editor(|editor, window, cx| {
 6951        editor.add_selection_below(&Default::default(), window, cx);
 6952        editor.add_selection_below(&Default::default(), window, cx);
 6953        editor.add_selection_below(&Default::default(), window, cx);
 6954    });
 6955
 6956    // initial state with two multi cursor groups
 6957    cx.assert_editor_state(indoc!(
 6958        r#"line onˇe
 6959           liˇne twˇo
 6960           liˇne thˇree
 6961           liˇne foˇur"#
 6962    ));
 6963
 6964    // add single cursor in middle - simulate opt click
 6965    cx.update_editor(|editor, window, cx| {
 6966        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6967        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6968        editor.end_selection(window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"line onˇe
 6973           liˇne twˇo
 6974           liˇneˇ thˇree
 6975           liˇne foˇur"#
 6976    ));
 6977
 6978    cx.update_editor(|editor, window, cx| {
 6979        editor.add_selection_above(&Default::default(), window, cx);
 6980    });
 6981
 6982    // test new added selection expands above and existing selection shrinks
 6983    cx.assert_editor_state(indoc!(
 6984        r#"line onˇe
 6985           liˇneˇ twˇo
 6986           liˇneˇ thˇree
 6987           line four"#
 6988    ));
 6989
 6990    cx.update_editor(|editor, window, cx| {
 6991        editor.add_selection_above(&Default::default(), window, cx);
 6992    });
 6993
 6994    // test new added selection expands above and existing selection shrinks
 6995    cx.assert_editor_state(indoc!(
 6996        r#"lineˇ onˇe
 6997           liˇneˇ twˇo
 6998           lineˇ three
 6999           line four"#
 7000    ));
 7001
 7002    // intial state with two selection groups
 7003    cx.set_state(indoc!(
 7004        r#"abcd
 7005           ef«ghˇ»
 7006           ijkl
 7007           «mˇ»nop"#
 7008    ));
 7009
 7010    cx.update_editor(|editor, window, cx| {
 7011        editor.add_selection_above(&Default::default(), window, cx);
 7012        editor.add_selection_above(&Default::default(), window, cx);
 7013    });
 7014
 7015    cx.assert_editor_state(indoc!(
 7016        r#"ab«cdˇ»
 7017           «eˇ»f«ghˇ»
 7018           «iˇ»jkl
 7019           «mˇ»nop"#
 7020    ));
 7021
 7022    // add single selection in middle - simulate opt drag
 7023    cx.update_editor(|editor, window, cx| {
 7024        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7025        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7026        editor.update_selection(
 7027            DisplayPoint::new(DisplayRow(2), 4),
 7028            0,
 7029            gpui::Point::<f32>::default(),
 7030            window,
 7031            cx,
 7032        );
 7033        editor.end_selection(window, cx);
 7034    });
 7035
 7036    cx.assert_editor_state(indoc!(
 7037        r#"ab«cdˇ»
 7038           «eˇ»f«ghˇ»
 7039           «iˇ»jk«lˇ»
 7040           «mˇ»nop"#
 7041    ));
 7042
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.add_selection_below(&Default::default(), window, cx);
 7045    });
 7046
 7047    // test new added selection expands below, others shrinks from above
 7048    cx.assert_editor_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           «iˇ»jk«lˇ»
 7052           «mˇ»no«pˇ»"#
 7053    ));
 7054}
 7055
 7056#[gpui::test]
 7057async fn test_select_next(cx: &mut TestAppContext) {
 7058    init_test(cx, |_| {});
 7059
 7060    let mut cx = EditorTestContext::new(cx).await;
 7061    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7062
 7063    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7066
 7067    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7068        .unwrap();
 7069    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7070
 7071    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7072    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7073
 7074    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7075    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7076
 7077    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7078        .unwrap();
 7079    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7080
 7081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7084
 7085    // Test selection direction should be preserved
 7086    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7087
 7088    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7089        .unwrap();
 7090    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7091}
 7092
 7093#[gpui::test]
 7094async fn test_select_all_matches(cx: &mut TestAppContext) {
 7095    init_test(cx, |_| {});
 7096
 7097    let mut cx = EditorTestContext::new(cx).await;
 7098
 7099    // Test caret-only selections
 7100    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7101    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7102        .unwrap();
 7103    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7104
 7105    // Test left-to-right selections
 7106    cx.set_state("abc\n«abcˇ»\nabc");
 7107    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7108        .unwrap();
 7109    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7110
 7111    // Test right-to-left selections
 7112    cx.set_state("abc\n«ˇabc»\nabc");
 7113    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7114        .unwrap();
 7115    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7116
 7117    // Test selecting whitespace with caret selection
 7118    cx.set_state("abc\nˇ   abc\nabc");
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7122
 7123    // Test selecting whitespace with left-to-right selection
 7124    cx.set_state("abc\n«ˇ  »abc\nabc");
 7125    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7126        .unwrap();
 7127    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7128
 7129    // Test no matches with right-to-left selection
 7130    cx.set_state("abc\n«  ˇ»abc\nabc");
 7131    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7132        .unwrap();
 7133    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7134
 7135    // Test with a single word and clip_at_line_ends=true (#29823)
 7136    cx.set_state("aˇbc");
 7137    cx.update_editor(|e, window, cx| {
 7138        e.set_clip_at_line_ends(true, cx);
 7139        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7140        e.set_clip_at_line_ends(false, cx);
 7141    });
 7142    cx.assert_editor_state("«abcˇ»");
 7143}
 7144
 7145#[gpui::test]
 7146async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7147    init_test(cx, |_| {});
 7148
 7149    let mut cx = EditorTestContext::new(cx).await;
 7150
 7151    let large_body_1 = "\nd".repeat(200);
 7152    let large_body_2 = "\ne".repeat(200);
 7153
 7154    cx.set_state(&format!(
 7155        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7156    ));
 7157    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7158        let scroll_position = editor.scroll_position(cx);
 7159        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7160        scroll_position
 7161    });
 7162
 7163    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7164        .unwrap();
 7165    cx.assert_editor_state(&format!(
 7166        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7167    ));
 7168    let scroll_position_after_selection =
 7169        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7170    assert_eq!(
 7171        initial_scroll_position, scroll_position_after_selection,
 7172        "Scroll position should not change after selecting all matches"
 7173    );
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179
 7180    let mut cx = EditorLspTestContext::new_rust(
 7181        lsp::ServerCapabilities {
 7182            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7183            ..Default::default()
 7184        },
 7185        cx,
 7186    )
 7187    .await;
 7188
 7189    cx.set_state(indoc! {"
 7190        line 1
 7191        line 2
 7192        linˇe 3
 7193        line 4
 7194        line 5
 7195    "});
 7196
 7197    // Make an edit
 7198    cx.update_editor(|editor, window, cx| {
 7199        editor.handle_input("X", window, cx);
 7200    });
 7201
 7202    // Move cursor to a different position
 7203    cx.update_editor(|editor, window, cx| {
 7204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7205            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7206        });
 7207    });
 7208
 7209    cx.assert_editor_state(indoc! {"
 7210        line 1
 7211        line 2
 7212        linXe 3
 7213        line 4
 7214        liˇne 5
 7215    "});
 7216
 7217    cx.lsp
 7218        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7219            Ok(Some(vec![lsp::TextEdit::new(
 7220                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7221                "PREFIX ".to_string(),
 7222            )]))
 7223        });
 7224
 7225    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7226        .unwrap()
 7227        .await
 7228        .unwrap();
 7229
 7230    cx.assert_editor_state(indoc! {"
 7231        PREFIX line 1
 7232        line 2
 7233        linXe 3
 7234        line 4
 7235        liˇne 5
 7236    "});
 7237
 7238    // Undo formatting
 7239    cx.update_editor(|editor, window, cx| {
 7240        editor.undo(&Default::default(), window, cx);
 7241    });
 7242
 7243    // Verify cursor moved back to position after edit
 7244    cx.assert_editor_state(indoc! {"
 7245        line 1
 7246        line 2
 7247        linXˇe 3
 7248        line 4
 7249        line 5
 7250    "});
 7251}
 7252
 7253#[gpui::test]
 7254async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7255    init_test(cx, |_| {});
 7256
 7257    let mut cx = EditorTestContext::new(cx).await;
 7258
 7259    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7260    cx.update_editor(|editor, window, cx| {
 7261        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7262    });
 7263
 7264    cx.set_state(indoc! {"
 7265        line 1
 7266        line 2
 7267        linˇe 3
 7268        line 4
 7269        line 5
 7270        line 6
 7271        line 7
 7272        line 8
 7273        line 9
 7274        line 10
 7275    "});
 7276
 7277    let snapshot = cx.buffer_snapshot();
 7278    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7279
 7280    cx.update(|_, cx| {
 7281        provider.update(cx, |provider, _| {
 7282            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7283                id: None,
 7284                edits: vec![(edit_position..edit_position, "X".into())],
 7285                edit_preview: None,
 7286            }))
 7287        })
 7288    });
 7289
 7290    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7291    cx.update_editor(|editor, window, cx| {
 7292        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7293    });
 7294
 7295    cx.assert_editor_state(indoc! {"
 7296        line 1
 7297        line 2
 7298        lineXˇ 3
 7299        line 4
 7300        line 5
 7301        line 6
 7302        line 7
 7303        line 8
 7304        line 9
 7305        line 10
 7306    "});
 7307
 7308    cx.update_editor(|editor, window, cx| {
 7309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7310            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7311        });
 7312    });
 7313
 7314    cx.assert_editor_state(indoc! {"
 7315        line 1
 7316        line 2
 7317        lineX 3
 7318        line 4
 7319        line 5
 7320        line 6
 7321        line 7
 7322        line 8
 7323        line 9
 7324        liˇne 10
 7325    "});
 7326
 7327    cx.update_editor(|editor, window, cx| {
 7328        editor.undo(&Default::default(), window, cx);
 7329    });
 7330
 7331    cx.assert_editor_state(indoc! {"
 7332        line 1
 7333        line 2
 7334        lineˇ 3
 7335        line 4
 7336        line 5
 7337        line 6
 7338        line 7
 7339        line 8
 7340        line 9
 7341        line 10
 7342    "});
 7343}
 7344
 7345#[gpui::test]
 7346async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7347    init_test(cx, |_| {});
 7348
 7349    let mut cx = EditorTestContext::new(cx).await;
 7350    cx.set_state(
 7351        r#"let foo = 2;
 7352lˇet foo = 2;
 7353let fooˇ = 2;
 7354let foo = 2;
 7355let foo = ˇ2;"#,
 7356    );
 7357
 7358    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7359        .unwrap();
 7360    cx.assert_editor_state(
 7361        r#"let foo = 2;
 7362«letˇ» foo = 2;
 7363let «fooˇ» = 2;
 7364let foo = 2;
 7365let foo = «2ˇ»;"#,
 7366    );
 7367
 7368    // noop for multiple selections with different contents
 7369    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7370        .unwrap();
 7371    cx.assert_editor_state(
 7372        r#"let foo = 2;
 7373«letˇ» foo = 2;
 7374let «fooˇ» = 2;
 7375let foo = 2;
 7376let foo = «2ˇ»;"#,
 7377    );
 7378
 7379    // Test last selection direction should be preserved
 7380    cx.set_state(
 7381        r#"let foo = 2;
 7382let foo = 2;
 7383let «fooˇ» = 2;
 7384let «ˇfoo» = 2;
 7385let foo = 2;"#,
 7386    );
 7387
 7388    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7389        .unwrap();
 7390    cx.assert_editor_state(
 7391        r#"let foo = 2;
 7392let foo = 2;
 7393let «fooˇ» = 2;
 7394let «ˇfoo» = 2;
 7395let «ˇfoo» = 2;"#,
 7396    );
 7397}
 7398
 7399#[gpui::test]
 7400async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7401    init_test(cx, |_| {});
 7402
 7403    let mut cx =
 7404        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7405
 7406    cx.assert_editor_state(indoc! {"
 7407        ˇbbb
 7408        ccc
 7409
 7410        bbb
 7411        ccc
 7412        "});
 7413    cx.dispatch_action(SelectPrevious::default());
 7414    cx.assert_editor_state(indoc! {"
 7415                «bbbˇ»
 7416                ccc
 7417
 7418                bbb
 7419                ccc
 7420                "});
 7421    cx.dispatch_action(SelectPrevious::default());
 7422    cx.assert_editor_state(indoc! {"
 7423                «bbbˇ»
 7424                ccc
 7425
 7426                «bbbˇ»
 7427                ccc
 7428                "});
 7429}
 7430
 7431#[gpui::test]
 7432async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7433    init_test(cx, |_| {});
 7434
 7435    let mut cx = EditorTestContext::new(cx).await;
 7436    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7437
 7438    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7439        .unwrap();
 7440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7441
 7442    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7443        .unwrap();
 7444    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7445
 7446    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7447    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7448
 7449    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7450    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7451
 7452    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7453        .unwrap();
 7454    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7455
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7459}
 7460
 7461#[gpui::test]
 7462async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7463    init_test(cx, |_| {});
 7464
 7465    let mut cx = EditorTestContext::new(cx).await;
 7466    cx.set_state("");
 7467
 7468    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7469        .unwrap();
 7470    cx.assert_editor_state("«aˇ»");
 7471    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7472        .unwrap();
 7473    cx.assert_editor_state("«aˇ»");
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state(
 7482        r#"let foo = 2;
 7483lˇet foo = 2;
 7484let fooˇ = 2;
 7485let foo = 2;
 7486let foo = ˇ2;"#,
 7487    );
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state(
 7492        r#"let foo = 2;
 7493«letˇ» foo = 2;
 7494let «fooˇ» = 2;
 7495let foo = 2;
 7496let foo = «2ˇ»;"#,
 7497    );
 7498
 7499    // noop for multiple selections with different contents
 7500    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7501        .unwrap();
 7502    cx.assert_editor_state(
 7503        r#"let foo = 2;
 7504«letˇ» foo = 2;
 7505let «fooˇ» = 2;
 7506let foo = 2;
 7507let foo = «2ˇ»;"#,
 7508    );
 7509}
 7510
 7511#[gpui::test]
 7512async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7513    init_test(cx, |_| {});
 7514
 7515    let mut cx = EditorTestContext::new(cx).await;
 7516    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7517
 7518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7519        .unwrap();
 7520    // selection direction is preserved
 7521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7522
 7523    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7524        .unwrap();
 7525    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7526
 7527    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7529
 7530    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7531    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7532
 7533    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7534        .unwrap();
 7535    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7536
 7537    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7538        .unwrap();
 7539    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7540}
 7541
 7542#[gpui::test]
 7543async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7544    init_test(cx, |_| {});
 7545
 7546    let language = Arc::new(Language::new(
 7547        LanguageConfig::default(),
 7548        Some(tree_sitter_rust::LANGUAGE.into()),
 7549    ));
 7550
 7551    let text = r#"
 7552        use mod1::mod2::{mod3, mod4};
 7553
 7554        fn fn_1(param1: bool, param2: &str) {
 7555            let var1 = "text";
 7556        }
 7557    "#
 7558    .unindent();
 7559
 7560    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7561    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7562    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7563
 7564    editor
 7565        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7566        .await;
 7567
 7568    editor.update_in(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7572                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7573                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7574            ]);
 7575        });
 7576        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7577    });
 7578    editor.update(cx, |editor, cx| {
 7579        assert_text_with_selections(
 7580            editor,
 7581            indoc! {r#"
 7582                use mod1::mod2::{mod3, «mod4ˇ»};
 7583
 7584                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7585                    let var1 = "«ˇtext»";
 7586                }
 7587            "#},
 7588            cx,
 7589        );
 7590    });
 7591
 7592    editor.update_in(cx, |editor, window, cx| {
 7593        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7594    });
 7595    editor.update(cx, |editor, cx| {
 7596        assert_text_with_selections(
 7597            editor,
 7598            indoc! {r#"
 7599                use mod1::mod2::«{mod3, mod4}ˇ»;
 7600
 7601                «ˇfn fn_1(param1: bool, param2: &str) {
 7602                    let var1 = "text";
 7603 7604            "#},
 7605            cx,
 7606        );
 7607    });
 7608
 7609    editor.update_in(cx, |editor, window, cx| {
 7610        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7611    });
 7612    assert_eq!(
 7613        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7614        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7615    );
 7616
 7617    // Trying to expand the selected syntax node one more time has no effect.
 7618    editor.update_in(cx, |editor, window, cx| {
 7619        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7620    });
 7621    assert_eq!(
 7622        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7623        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7624    );
 7625
 7626    editor.update_in(cx, |editor, window, cx| {
 7627        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7628    });
 7629    editor.update(cx, |editor, cx| {
 7630        assert_text_with_selections(
 7631            editor,
 7632            indoc! {r#"
 7633                use mod1::mod2::«{mod3, mod4}ˇ»;
 7634
 7635                «ˇfn fn_1(param1: bool, param2: &str) {
 7636                    let var1 = "text";
 7637 7638            "#},
 7639            cx,
 7640        );
 7641    });
 7642
 7643    editor.update_in(cx, |editor, window, cx| {
 7644        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7645    });
 7646    editor.update(cx, |editor, cx| {
 7647        assert_text_with_selections(
 7648            editor,
 7649            indoc! {r#"
 7650                use mod1::mod2::{mod3, «mod4ˇ»};
 7651
 7652                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7653                    let var1 = "«ˇtext»";
 7654                }
 7655            "#},
 7656            cx,
 7657        );
 7658    });
 7659
 7660    editor.update_in(cx, |editor, window, cx| {
 7661        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7662    });
 7663    editor.update(cx, |editor, cx| {
 7664        assert_text_with_selections(
 7665            editor,
 7666            indoc! {r#"
 7667                use mod1::mod2::{mod3, mo«ˇ»d4};
 7668
 7669                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7670                    let var1 = "te«ˇ»xt";
 7671                }
 7672            "#},
 7673            cx,
 7674        );
 7675    });
 7676
 7677    // Trying to shrink the selected syntax node one more time has no effect.
 7678    editor.update_in(cx, |editor, window, cx| {
 7679        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7680    });
 7681    editor.update_in(cx, |editor, _, cx| {
 7682        assert_text_with_selections(
 7683            editor,
 7684            indoc! {r#"
 7685                use mod1::mod2::{mod3, mo«ˇ»d4};
 7686
 7687                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7688                    let var1 = "te«ˇ»xt";
 7689                }
 7690            "#},
 7691            cx,
 7692        );
 7693    });
 7694
 7695    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7696    // a fold.
 7697    editor.update_in(cx, |editor, window, cx| {
 7698        editor.fold_creases(
 7699            vec![
 7700                Crease::simple(
 7701                    Point::new(0, 21)..Point::new(0, 24),
 7702                    FoldPlaceholder::test(),
 7703                ),
 7704                Crease::simple(
 7705                    Point::new(3, 20)..Point::new(3, 22),
 7706                    FoldPlaceholder::test(),
 7707                ),
 7708            ],
 7709            true,
 7710            window,
 7711            cx,
 7712        );
 7713        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7714    });
 7715    editor.update(cx, |editor, cx| {
 7716        assert_text_with_selections(
 7717            editor,
 7718            indoc! {r#"
 7719                use mod1::mod2::«{mod3, mod4}ˇ»;
 7720
 7721                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7722                    let var1 = "«ˇtext»";
 7723                }
 7724            "#},
 7725            cx,
 7726        );
 7727    });
 7728}
 7729
 7730#[gpui::test]
 7731async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7732    init_test(cx, |_| {});
 7733
 7734    let language = Arc::new(Language::new(
 7735        LanguageConfig::default(),
 7736        Some(tree_sitter_rust::LANGUAGE.into()),
 7737    ));
 7738
 7739    let text = "let a = 2;";
 7740
 7741    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7742    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7743    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7744
 7745    editor
 7746        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7747        .await;
 7748
 7749    // Test case 1: Cursor at end of word
 7750    editor.update_in(cx, |editor, window, cx| {
 7751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7752            s.select_display_ranges([
 7753                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7754            ]);
 7755        });
 7756    });
 7757    editor.update(cx, |editor, cx| {
 7758        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7759    });
 7760    editor.update_in(cx, |editor, window, cx| {
 7761        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7762    });
 7763    editor.update(cx, |editor, cx| {
 7764        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7765    });
 7766    editor.update_in(cx, |editor, window, cx| {
 7767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7768    });
 7769    editor.update(cx, |editor, cx| {
 7770        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7771    });
 7772
 7773    // Test case 2: Cursor at end of statement
 7774    editor.update_in(cx, |editor, window, cx| {
 7775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7776            s.select_display_ranges([
 7777                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7778            ]);
 7779        });
 7780    });
 7781    editor.update(cx, |editor, cx| {
 7782        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7783    });
 7784    editor.update_in(cx, |editor, window, cx| {
 7785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7786    });
 7787    editor.update(cx, |editor, cx| {
 7788        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7789    });
 7790}
 7791
 7792#[gpui::test]
 7793async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7794    init_test(cx, |_| {});
 7795
 7796    let language = Arc::new(Language::new(
 7797        LanguageConfig::default(),
 7798        Some(tree_sitter_rust::LANGUAGE.into()),
 7799    ));
 7800
 7801    let text = r#"
 7802        use mod1::mod2::{mod3, mod4};
 7803
 7804        fn fn_1(param1: bool, param2: &str) {
 7805            let var1 = "hello world";
 7806        }
 7807    "#
 7808    .unindent();
 7809
 7810    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7811    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7812    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7813
 7814    editor
 7815        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7816        .await;
 7817
 7818    // Test 1: Cursor on a letter of a string word
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update_in(cx, |editor, window, cx| {
 7827        assert_text_with_selections(
 7828            editor,
 7829            indoc! {r#"
 7830                use mod1::mod2::{mod3, mod4};
 7831
 7832                fn fn_1(param1: bool, param2: &str) {
 7833                    let var1 = "hˇello world";
 7834                }
 7835            "#},
 7836            cx,
 7837        );
 7838        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7839        assert_text_with_selections(
 7840            editor,
 7841            indoc! {r#"
 7842                use mod1::mod2::{mod3, mod4};
 7843
 7844                fn fn_1(param1: bool, param2: &str) {
 7845                    let var1 = "«ˇhello» world";
 7846                }
 7847            "#},
 7848            cx,
 7849        );
 7850    });
 7851
 7852    // Test 2: Partial selection within a word
 7853    editor.update_in(cx, |editor, window, cx| {
 7854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7855            s.select_display_ranges([
 7856                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7857            ]);
 7858        });
 7859    });
 7860    editor.update_in(cx, |editor, window, cx| {
 7861        assert_text_with_selections(
 7862            editor,
 7863            indoc! {r#"
 7864                use mod1::mod2::{mod3, mod4};
 7865
 7866                fn fn_1(param1: bool, param2: &str) {
 7867                    let var1 = "h«elˇ»lo world";
 7868                }
 7869            "#},
 7870            cx,
 7871        );
 7872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7873        assert_text_with_selections(
 7874            editor,
 7875            indoc! {r#"
 7876                use mod1::mod2::{mod3, mod4};
 7877
 7878                fn fn_1(param1: bool, param2: &str) {
 7879                    let var1 = "«ˇhello» world";
 7880                }
 7881            "#},
 7882            cx,
 7883        );
 7884    });
 7885
 7886    // Test 3: Complete word already selected
 7887    editor.update_in(cx, |editor, window, cx| {
 7888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7889            s.select_display_ranges([
 7890                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7891            ]);
 7892        });
 7893    });
 7894    editor.update_in(cx, |editor, window, cx| {
 7895        assert_text_with_selections(
 7896            editor,
 7897            indoc! {r#"
 7898                use mod1::mod2::{mod3, mod4};
 7899
 7900                fn fn_1(param1: bool, param2: &str) {
 7901                    let var1 = "«helloˇ» world";
 7902                }
 7903            "#},
 7904            cx,
 7905        );
 7906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7907        assert_text_with_selections(
 7908            editor,
 7909            indoc! {r#"
 7910                use mod1::mod2::{mod3, mod4};
 7911
 7912                fn fn_1(param1: bool, param2: &str) {
 7913                    let var1 = "«hello worldˇ»";
 7914                }
 7915            "#},
 7916            cx,
 7917        );
 7918    });
 7919
 7920    // Test 4: Selection spanning across words
 7921    editor.update_in(cx, |editor, window, cx| {
 7922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7923            s.select_display_ranges([
 7924                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7925            ]);
 7926        });
 7927    });
 7928    editor.update_in(cx, |editor, window, cx| {
 7929        assert_text_with_selections(
 7930            editor,
 7931            indoc! {r#"
 7932                use mod1::mod2::{mod3, mod4};
 7933
 7934                fn fn_1(param1: bool, param2: &str) {
 7935                    let var1 = "hel«lo woˇ»rld";
 7936                }
 7937            "#},
 7938            cx,
 7939        );
 7940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7941        assert_text_with_selections(
 7942            editor,
 7943            indoc! {r#"
 7944                use mod1::mod2::{mod3, mod4};
 7945
 7946                fn fn_1(param1: bool, param2: &str) {
 7947                    let var1 = "«ˇhello world»";
 7948                }
 7949            "#},
 7950            cx,
 7951        );
 7952    });
 7953
 7954    // Test 5: Expansion beyond string
 7955    editor.update_in(cx, |editor, window, cx| {
 7956        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7957        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7958        assert_text_with_selections(
 7959            editor,
 7960            indoc! {r#"
 7961                use mod1::mod2::{mod3, mod4};
 7962
 7963                fn fn_1(param1: bool, param2: &str) {
 7964                    «ˇlet var1 = "hello world";»
 7965                }
 7966            "#},
 7967            cx,
 7968        );
 7969    });
 7970}
 7971
 7972#[gpui::test]
 7973async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7974    init_test(cx, |_| {});
 7975
 7976    let base_text = r#"
 7977        impl A {
 7978            // this is an uncommitted comment
 7979
 7980            fn b() {
 7981                c();
 7982            }
 7983
 7984            // this is another uncommitted comment
 7985
 7986            fn d() {
 7987                // e
 7988                // f
 7989            }
 7990        }
 7991
 7992        fn g() {
 7993            // h
 7994        }
 7995    "#
 7996    .unindent();
 7997
 7998    let text = r#"
 7999        ˇimpl A {
 8000
 8001            fn b() {
 8002                c();
 8003            }
 8004
 8005            fn d() {
 8006                // e
 8007                // f
 8008            }
 8009        }
 8010
 8011        fn g() {
 8012            // h
 8013        }
 8014    "#
 8015    .unindent();
 8016
 8017    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8018    cx.set_state(&text);
 8019    cx.set_head_text(&base_text);
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8022    });
 8023
 8024    cx.assert_state_with_diff(
 8025        "
 8026        ˇimpl A {
 8027      -     // this is an uncommitted comment
 8028
 8029            fn b() {
 8030                c();
 8031            }
 8032
 8033      -     // this is another uncommitted comment
 8034      -
 8035            fn d() {
 8036                // e
 8037                // f
 8038            }
 8039        }
 8040
 8041        fn g() {
 8042            // h
 8043        }
 8044    "
 8045        .unindent(),
 8046    );
 8047
 8048    let expected_display_text = "
 8049        impl A {
 8050            // this is an uncommitted comment
 8051
 8052            fn b() {
 8053 8054            }
 8055
 8056            // this is another uncommitted comment
 8057
 8058            fn d() {
 8059 8060            }
 8061        }
 8062
 8063        fn g() {
 8064 8065        }
 8066        "
 8067    .unindent();
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8071        assert_eq!(editor.display_text(cx), expected_display_text);
 8072    });
 8073}
 8074
 8075#[gpui::test]
 8076async fn test_autoindent(cx: &mut TestAppContext) {
 8077    init_test(cx, |_| {});
 8078
 8079    let language = Arc::new(
 8080        Language::new(
 8081            LanguageConfig {
 8082                brackets: BracketPairConfig {
 8083                    pairs: vec![
 8084                        BracketPair {
 8085                            start: "{".to_string(),
 8086                            end: "}".to_string(),
 8087                            close: false,
 8088                            surround: false,
 8089                            newline: true,
 8090                        },
 8091                        BracketPair {
 8092                            start: "(".to_string(),
 8093                            end: ")".to_string(),
 8094                            close: false,
 8095                            surround: false,
 8096                            newline: true,
 8097                        },
 8098                    ],
 8099                    ..Default::default()
 8100                },
 8101                ..Default::default()
 8102            },
 8103            Some(tree_sitter_rust::LANGUAGE.into()),
 8104        )
 8105        .with_indents_query(
 8106            r#"
 8107                (_ "(" ")" @end) @indent
 8108                (_ "{" "}" @end) @indent
 8109            "#,
 8110        )
 8111        .unwrap(),
 8112    );
 8113
 8114    let text = "fn a() {}";
 8115
 8116    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8117    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8118    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8119    editor
 8120        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8121        .await;
 8122
 8123    editor.update_in(cx, |editor, window, cx| {
 8124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8125            s.select_ranges([5..5, 8..8, 9..9])
 8126        });
 8127        editor.newline(&Newline, window, cx);
 8128        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8129        assert_eq!(
 8130            editor.selections.ranges(cx),
 8131            &[
 8132                Point::new(1, 4)..Point::new(1, 4),
 8133                Point::new(3, 4)..Point::new(3, 4),
 8134                Point::new(5, 0)..Point::new(5, 0)
 8135            ]
 8136        );
 8137    });
 8138}
 8139
 8140#[gpui::test]
 8141async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8142    init_test(cx, |_| {});
 8143
 8144    {
 8145        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8146        cx.set_state(indoc! {"
 8147            impl A {
 8148
 8149                fn b() {}
 8150
 8151            «fn c() {
 8152
 8153            }ˇ»
 8154            }
 8155        "});
 8156
 8157        cx.update_editor(|editor, window, cx| {
 8158            editor.autoindent(&Default::default(), window, cx);
 8159        });
 8160
 8161        cx.assert_editor_state(indoc! {"
 8162            impl A {
 8163
 8164                fn b() {}
 8165
 8166                «fn c() {
 8167
 8168                }ˇ»
 8169            }
 8170        "});
 8171    }
 8172
 8173    {
 8174        let mut cx = EditorTestContext::new_multibuffer(
 8175            cx,
 8176            [indoc! { "
 8177                impl A {
 8178                «
 8179                // a
 8180                fn b(){}
 8181                »
 8182                «
 8183                    }
 8184                    fn c(){}
 8185                »
 8186            "}],
 8187        );
 8188
 8189        let buffer = cx.update_editor(|editor, _, cx| {
 8190            let buffer = editor.buffer().update(cx, |buffer, _| {
 8191                buffer.all_buffers().iter().next().unwrap().clone()
 8192            });
 8193            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8194            buffer
 8195        });
 8196
 8197        cx.run_until_parked();
 8198        cx.update_editor(|editor, window, cx| {
 8199            editor.select_all(&Default::default(), window, cx);
 8200            editor.autoindent(&Default::default(), window, cx)
 8201        });
 8202        cx.run_until_parked();
 8203
 8204        cx.update(|_, cx| {
 8205            assert_eq!(
 8206                buffer.read(cx).text(),
 8207                indoc! { "
 8208                    impl A {
 8209
 8210                        // a
 8211                        fn b(){}
 8212
 8213
 8214                    }
 8215                    fn c(){}
 8216
 8217                " }
 8218            )
 8219        });
 8220    }
 8221}
 8222
 8223#[gpui::test]
 8224async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8225    init_test(cx, |_| {});
 8226
 8227    let mut cx = EditorTestContext::new(cx).await;
 8228
 8229    let language = Arc::new(Language::new(
 8230        LanguageConfig {
 8231            brackets: BracketPairConfig {
 8232                pairs: vec![
 8233                    BracketPair {
 8234                        start: "{".to_string(),
 8235                        end: "}".to_string(),
 8236                        close: true,
 8237                        surround: true,
 8238                        newline: true,
 8239                    },
 8240                    BracketPair {
 8241                        start: "(".to_string(),
 8242                        end: ")".to_string(),
 8243                        close: true,
 8244                        surround: true,
 8245                        newline: true,
 8246                    },
 8247                    BracketPair {
 8248                        start: "/*".to_string(),
 8249                        end: " */".to_string(),
 8250                        close: true,
 8251                        surround: true,
 8252                        newline: true,
 8253                    },
 8254                    BracketPair {
 8255                        start: "[".to_string(),
 8256                        end: "]".to_string(),
 8257                        close: false,
 8258                        surround: false,
 8259                        newline: true,
 8260                    },
 8261                    BracketPair {
 8262                        start: "\"".to_string(),
 8263                        end: "\"".to_string(),
 8264                        close: true,
 8265                        surround: true,
 8266                        newline: false,
 8267                    },
 8268                    BracketPair {
 8269                        start: "<".to_string(),
 8270                        end: ">".to_string(),
 8271                        close: false,
 8272                        surround: true,
 8273                        newline: true,
 8274                    },
 8275                ],
 8276                ..Default::default()
 8277            },
 8278            autoclose_before: "})]".to_string(),
 8279            ..Default::default()
 8280        },
 8281        Some(tree_sitter_rust::LANGUAGE.into()),
 8282    ));
 8283
 8284    cx.language_registry().add(language.clone());
 8285    cx.update_buffer(|buffer, cx| {
 8286        buffer.set_language(Some(language), cx);
 8287    });
 8288
 8289    cx.set_state(
 8290        &r#"
 8291            🏀ˇ
 8292            εˇ
 8293            ❤️ˇ
 8294        "#
 8295        .unindent(),
 8296    );
 8297
 8298    // autoclose multiple nested brackets at multiple cursors
 8299    cx.update_editor(|editor, window, cx| {
 8300        editor.handle_input("{", window, cx);
 8301        editor.handle_input("{", window, cx);
 8302        editor.handle_input("{", window, cx);
 8303    });
 8304    cx.assert_editor_state(
 8305        &"
 8306            🏀{{{ˇ}}}
 8307            ε{{{ˇ}}}
 8308            ❤️{{{ˇ}}}
 8309        "
 8310        .unindent(),
 8311    );
 8312
 8313    // insert a different closing bracket
 8314    cx.update_editor(|editor, window, cx| {
 8315        editor.handle_input(")", window, cx);
 8316    });
 8317    cx.assert_editor_state(
 8318        &"
 8319            🏀{{{)ˇ}}}
 8320            ε{{{)ˇ}}}
 8321            ❤️{{{)ˇ}}}
 8322        "
 8323        .unindent(),
 8324    );
 8325
 8326    // skip over the auto-closed brackets when typing a closing bracket
 8327    cx.update_editor(|editor, window, cx| {
 8328        editor.move_right(&MoveRight, window, cx);
 8329        editor.handle_input("}", window, cx);
 8330        editor.handle_input("}", window, cx);
 8331        editor.handle_input("}", window, cx);
 8332    });
 8333    cx.assert_editor_state(
 8334        &"
 8335            🏀{{{)}}}}ˇ
 8336            ε{{{)}}}}ˇ
 8337            ❤️{{{)}}}}ˇ
 8338        "
 8339        .unindent(),
 8340    );
 8341
 8342    // autoclose multi-character pairs
 8343    cx.set_state(
 8344        &"
 8345            ˇ
 8346            ˇ
 8347        "
 8348        .unindent(),
 8349    );
 8350    cx.update_editor(|editor, window, cx| {
 8351        editor.handle_input("/", window, cx);
 8352        editor.handle_input("*", window, cx);
 8353    });
 8354    cx.assert_editor_state(
 8355        &"
 8356            /*ˇ */
 8357            /*ˇ */
 8358        "
 8359        .unindent(),
 8360    );
 8361
 8362    // one cursor autocloses a multi-character pair, one cursor
 8363    // does not autoclose.
 8364    cx.set_state(
 8365        &"
 8366 8367            ˇ
 8368        "
 8369        .unindent(),
 8370    );
 8371    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8372    cx.assert_editor_state(
 8373        &"
 8374            /*ˇ */
 8375 8376        "
 8377        .unindent(),
 8378    );
 8379
 8380    // Don't autoclose if the next character isn't whitespace and isn't
 8381    // listed in the language's "autoclose_before" section.
 8382    cx.set_state("ˇa b");
 8383    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8384    cx.assert_editor_state("{ˇa b");
 8385
 8386    // Don't autoclose if `close` is false for the bracket pair
 8387    cx.set_state("ˇ");
 8388    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8389    cx.assert_editor_state("");
 8390
 8391    // Surround with brackets if text is selected
 8392    cx.set_state("«aˇ» b");
 8393    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8394    cx.assert_editor_state("{«aˇ»} b");
 8395
 8396    // Autoclose when not immediately after a word character
 8397    cx.set_state("a ˇ");
 8398    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8399    cx.assert_editor_state("a \"ˇ\"");
 8400
 8401    // Autoclose pair where the start and end characters are the same
 8402    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8403    cx.assert_editor_state("a \"\"ˇ");
 8404
 8405    // Don't autoclose when immediately after a word character
 8406    cx.set_state("");
 8407    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8408    cx.assert_editor_state("a\"ˇ");
 8409
 8410    // Do autoclose when after a non-word character
 8411    cx.set_state("");
 8412    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8413    cx.assert_editor_state("{\"ˇ\"");
 8414
 8415    // Non identical pairs autoclose regardless of preceding character
 8416    cx.set_state("");
 8417    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8418    cx.assert_editor_state("a{ˇ}");
 8419
 8420    // Don't autoclose pair if autoclose is disabled
 8421    cx.set_state("ˇ");
 8422    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8423    cx.assert_editor_state("");
 8424
 8425    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8426    cx.set_state("«aˇ» b");
 8427    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8428    cx.assert_editor_state("<«aˇ»> b");
 8429}
 8430
 8431#[gpui::test]
 8432async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8433    init_test(cx, |settings| {
 8434        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8435    });
 8436
 8437    let mut cx = EditorTestContext::new(cx).await;
 8438
 8439    let language = Arc::new(Language::new(
 8440        LanguageConfig {
 8441            brackets: BracketPairConfig {
 8442                pairs: vec![
 8443                    BracketPair {
 8444                        start: "{".to_string(),
 8445                        end: "}".to_string(),
 8446                        close: true,
 8447                        surround: true,
 8448                        newline: true,
 8449                    },
 8450                    BracketPair {
 8451                        start: "(".to_string(),
 8452                        end: ")".to_string(),
 8453                        close: true,
 8454                        surround: true,
 8455                        newline: true,
 8456                    },
 8457                    BracketPair {
 8458                        start: "[".to_string(),
 8459                        end: "]".to_string(),
 8460                        close: false,
 8461                        surround: false,
 8462                        newline: true,
 8463                    },
 8464                ],
 8465                ..Default::default()
 8466            },
 8467            autoclose_before: "})]".to_string(),
 8468            ..Default::default()
 8469        },
 8470        Some(tree_sitter_rust::LANGUAGE.into()),
 8471    ));
 8472
 8473    cx.language_registry().add(language.clone());
 8474    cx.update_buffer(|buffer, cx| {
 8475        buffer.set_language(Some(language), cx);
 8476    });
 8477
 8478    cx.set_state(
 8479        &"
 8480            ˇ
 8481            ˇ
 8482            ˇ
 8483        "
 8484        .unindent(),
 8485    );
 8486
 8487    // ensure only matching closing brackets are skipped over
 8488    cx.update_editor(|editor, window, cx| {
 8489        editor.handle_input("}", window, cx);
 8490        editor.move_left(&MoveLeft, window, cx);
 8491        editor.handle_input(")", window, cx);
 8492        editor.move_left(&MoveLeft, window, cx);
 8493    });
 8494    cx.assert_editor_state(
 8495        &"
 8496            ˇ)}
 8497            ˇ)}
 8498            ˇ)}
 8499        "
 8500        .unindent(),
 8501    );
 8502
 8503    // skip-over closing brackets at multiple cursors
 8504    cx.update_editor(|editor, window, cx| {
 8505        editor.handle_input(")", window, cx);
 8506        editor.handle_input("}", window, cx);
 8507    });
 8508    cx.assert_editor_state(
 8509        &"
 8510            )}ˇ
 8511            )}ˇ
 8512            )}ˇ
 8513        "
 8514        .unindent(),
 8515    );
 8516
 8517    // ignore non-close brackets
 8518    cx.update_editor(|editor, window, cx| {
 8519        editor.handle_input("]", window, cx);
 8520        editor.move_left(&MoveLeft, window, cx);
 8521        editor.handle_input("]", window, cx);
 8522    });
 8523    cx.assert_editor_state(
 8524        &"
 8525            )}]ˇ]
 8526            )}]ˇ]
 8527            )}]ˇ]
 8528        "
 8529        .unindent(),
 8530    );
 8531}
 8532
 8533#[gpui::test]
 8534async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8535    init_test(cx, |_| {});
 8536
 8537    let mut cx = EditorTestContext::new(cx).await;
 8538
 8539    let html_language = Arc::new(
 8540        Language::new(
 8541            LanguageConfig {
 8542                name: "HTML".into(),
 8543                brackets: BracketPairConfig {
 8544                    pairs: vec![
 8545                        BracketPair {
 8546                            start: "<".into(),
 8547                            end: ">".into(),
 8548                            close: true,
 8549                            ..Default::default()
 8550                        },
 8551                        BracketPair {
 8552                            start: "{".into(),
 8553                            end: "}".into(),
 8554                            close: true,
 8555                            ..Default::default()
 8556                        },
 8557                        BracketPair {
 8558                            start: "(".into(),
 8559                            end: ")".into(),
 8560                            close: true,
 8561                            ..Default::default()
 8562                        },
 8563                    ],
 8564                    ..Default::default()
 8565                },
 8566                autoclose_before: "})]>".into(),
 8567                ..Default::default()
 8568            },
 8569            Some(tree_sitter_html::LANGUAGE.into()),
 8570        )
 8571        .with_injection_query(
 8572            r#"
 8573            (script_element
 8574                (raw_text) @injection.content
 8575                (#set! injection.language "javascript"))
 8576            "#,
 8577        )
 8578        .unwrap(),
 8579    );
 8580
 8581    let javascript_language = Arc::new(Language::new(
 8582        LanguageConfig {
 8583            name: "JavaScript".into(),
 8584            brackets: BracketPairConfig {
 8585                pairs: vec![
 8586                    BracketPair {
 8587                        start: "/*".into(),
 8588                        end: " */".into(),
 8589                        close: true,
 8590                        ..Default::default()
 8591                    },
 8592                    BracketPair {
 8593                        start: "{".into(),
 8594                        end: "}".into(),
 8595                        close: true,
 8596                        ..Default::default()
 8597                    },
 8598                    BracketPair {
 8599                        start: "(".into(),
 8600                        end: ")".into(),
 8601                        close: true,
 8602                        ..Default::default()
 8603                    },
 8604                ],
 8605                ..Default::default()
 8606            },
 8607            autoclose_before: "})]>".into(),
 8608            ..Default::default()
 8609        },
 8610        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8611    ));
 8612
 8613    cx.language_registry().add(html_language.clone());
 8614    cx.language_registry().add(javascript_language.clone());
 8615    cx.executor().run_until_parked();
 8616
 8617    cx.update_buffer(|buffer, cx| {
 8618        buffer.set_language(Some(html_language), cx);
 8619    });
 8620
 8621    cx.set_state(
 8622        &r#"
 8623            <body>ˇ
 8624                <script>
 8625                    var x = 1;ˇ
 8626                </script>
 8627            </body>ˇ
 8628        "#
 8629        .unindent(),
 8630    );
 8631
 8632    // Precondition: different languages are active at different locations.
 8633    cx.update_editor(|editor, window, cx| {
 8634        let snapshot = editor.snapshot(window, cx);
 8635        let cursors = editor.selections.ranges::<usize>(cx);
 8636        let languages = cursors
 8637            .iter()
 8638            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8639            .collect::<Vec<_>>();
 8640        assert_eq!(
 8641            languages,
 8642            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8643        );
 8644    });
 8645
 8646    // Angle brackets autoclose in HTML, but not JavaScript.
 8647    cx.update_editor(|editor, window, cx| {
 8648        editor.handle_input("<", window, cx);
 8649        editor.handle_input("a", window, cx);
 8650    });
 8651    cx.assert_editor_state(
 8652        &r#"
 8653            <body><aˇ>
 8654                <script>
 8655                    var x = 1;<aˇ
 8656                </script>
 8657            </body><aˇ>
 8658        "#
 8659        .unindent(),
 8660    );
 8661
 8662    // Curly braces and parens autoclose in both HTML and JavaScript.
 8663    cx.update_editor(|editor, window, cx| {
 8664        editor.handle_input(" b=", window, cx);
 8665        editor.handle_input("{", window, cx);
 8666        editor.handle_input("c", window, cx);
 8667        editor.handle_input("(", window, cx);
 8668    });
 8669    cx.assert_editor_state(
 8670        &r#"
 8671            <body><a b={c(ˇ)}>
 8672                <script>
 8673                    var x = 1;<a b={c(ˇ)}
 8674                </script>
 8675            </body><a b={c(ˇ)}>
 8676        "#
 8677        .unindent(),
 8678    );
 8679
 8680    // Brackets that were already autoclosed are skipped.
 8681    cx.update_editor(|editor, window, cx| {
 8682        editor.handle_input(")", window, cx);
 8683        editor.handle_input("d", window, cx);
 8684        editor.handle_input("}", window, cx);
 8685    });
 8686    cx.assert_editor_state(
 8687        &r#"
 8688            <body><a b={c()d}ˇ>
 8689                <script>
 8690                    var x = 1;<a b={c()d}ˇ
 8691                </script>
 8692            </body><a b={c()d}ˇ>
 8693        "#
 8694        .unindent(),
 8695    );
 8696    cx.update_editor(|editor, window, cx| {
 8697        editor.handle_input(">", window, cx);
 8698    });
 8699    cx.assert_editor_state(
 8700        &r#"
 8701            <body><a b={c()d}>ˇ
 8702                <script>
 8703                    var x = 1;<a b={c()d}>ˇ
 8704                </script>
 8705            </body><a b={c()d}>ˇ
 8706        "#
 8707        .unindent(),
 8708    );
 8709
 8710    // Reset
 8711    cx.set_state(
 8712        &r#"
 8713            <body>ˇ
 8714                <script>
 8715                    var x = 1;ˇ
 8716                </script>
 8717            </body>ˇ
 8718        "#
 8719        .unindent(),
 8720    );
 8721
 8722    cx.update_editor(|editor, window, cx| {
 8723        editor.handle_input("<", window, cx);
 8724    });
 8725    cx.assert_editor_state(
 8726        &r#"
 8727            <body><ˇ>
 8728                <script>
 8729                    var x = 1;<ˇ
 8730                </script>
 8731            </body><ˇ>
 8732        "#
 8733        .unindent(),
 8734    );
 8735
 8736    // When backspacing, the closing angle brackets are removed.
 8737    cx.update_editor(|editor, window, cx| {
 8738        editor.backspace(&Backspace, window, cx);
 8739    });
 8740    cx.assert_editor_state(
 8741        &r#"
 8742            <body>ˇ
 8743                <script>
 8744                    var x = 1;ˇ
 8745                </script>
 8746            </body>ˇ
 8747        "#
 8748        .unindent(),
 8749    );
 8750
 8751    // Block comments autoclose in JavaScript, but not HTML.
 8752    cx.update_editor(|editor, window, cx| {
 8753        editor.handle_input("/", window, cx);
 8754        editor.handle_input("*", window, cx);
 8755    });
 8756    cx.assert_editor_state(
 8757        &r#"
 8758            <body>/*ˇ
 8759                <script>
 8760                    var x = 1;/*ˇ */
 8761                </script>
 8762            </body>/*ˇ
 8763        "#
 8764        .unindent(),
 8765    );
 8766}
 8767
 8768#[gpui::test]
 8769async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8770    init_test(cx, |_| {});
 8771
 8772    let mut cx = EditorTestContext::new(cx).await;
 8773
 8774    let rust_language = Arc::new(
 8775        Language::new(
 8776            LanguageConfig {
 8777                name: "Rust".into(),
 8778                brackets: serde_json::from_value(json!([
 8779                    { "start": "{", "end": "}", "close": true, "newline": true },
 8780                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8781                ]))
 8782                .unwrap(),
 8783                autoclose_before: "})]>".into(),
 8784                ..Default::default()
 8785            },
 8786            Some(tree_sitter_rust::LANGUAGE.into()),
 8787        )
 8788        .with_override_query("(string_literal) @string")
 8789        .unwrap(),
 8790    );
 8791
 8792    cx.language_registry().add(rust_language.clone());
 8793    cx.update_buffer(|buffer, cx| {
 8794        buffer.set_language(Some(rust_language), cx);
 8795    });
 8796
 8797    cx.set_state(
 8798        &r#"
 8799            let x = ˇ
 8800        "#
 8801        .unindent(),
 8802    );
 8803
 8804    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8805    cx.update_editor(|editor, window, cx| {
 8806        editor.handle_input("\"", window, cx);
 8807    });
 8808    cx.assert_editor_state(
 8809        &r#"
 8810            let x = "ˇ"
 8811        "#
 8812        .unindent(),
 8813    );
 8814
 8815    // Inserting another quotation mark. The cursor moves across the existing
 8816    // automatically-inserted quotation mark.
 8817    cx.update_editor(|editor, window, cx| {
 8818        editor.handle_input("\"", window, cx);
 8819    });
 8820    cx.assert_editor_state(
 8821        &r#"
 8822            let x = ""ˇ
 8823        "#
 8824        .unindent(),
 8825    );
 8826
 8827    // Reset
 8828    cx.set_state(
 8829        &r#"
 8830            let x = ˇ
 8831        "#
 8832        .unindent(),
 8833    );
 8834
 8835    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8836    cx.update_editor(|editor, window, cx| {
 8837        editor.handle_input("\"", window, cx);
 8838        editor.handle_input(" ", window, cx);
 8839        editor.move_left(&Default::default(), window, cx);
 8840        editor.handle_input("\\", window, cx);
 8841        editor.handle_input("\"", window, cx);
 8842    });
 8843    cx.assert_editor_state(
 8844        &r#"
 8845            let x = "\"ˇ "
 8846        "#
 8847        .unindent(),
 8848    );
 8849
 8850    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8851    // mark. Nothing is inserted.
 8852    cx.update_editor(|editor, window, cx| {
 8853        editor.move_right(&Default::default(), window, cx);
 8854        editor.handle_input("\"", window, cx);
 8855    });
 8856    cx.assert_editor_state(
 8857        &r#"
 8858            let x = "\" "ˇ
 8859        "#
 8860        .unindent(),
 8861    );
 8862}
 8863
 8864#[gpui::test]
 8865async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8866    init_test(cx, |_| {});
 8867
 8868    let language = Arc::new(Language::new(
 8869        LanguageConfig {
 8870            brackets: BracketPairConfig {
 8871                pairs: vec![
 8872                    BracketPair {
 8873                        start: "{".to_string(),
 8874                        end: "}".to_string(),
 8875                        close: true,
 8876                        surround: true,
 8877                        newline: true,
 8878                    },
 8879                    BracketPair {
 8880                        start: "/* ".to_string(),
 8881                        end: "*/".to_string(),
 8882                        close: true,
 8883                        surround: true,
 8884                        ..Default::default()
 8885                    },
 8886                ],
 8887                ..Default::default()
 8888            },
 8889            ..Default::default()
 8890        },
 8891        Some(tree_sitter_rust::LANGUAGE.into()),
 8892    ));
 8893
 8894    let text = r#"
 8895        a
 8896        b
 8897        c
 8898    "#
 8899    .unindent();
 8900
 8901    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8902    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8903    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8904    editor
 8905        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8906        .await;
 8907
 8908    editor.update_in(cx, |editor, window, cx| {
 8909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8910            s.select_display_ranges([
 8911                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8912                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8913                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8914            ])
 8915        });
 8916
 8917        editor.handle_input("{", window, cx);
 8918        editor.handle_input("{", window, cx);
 8919        editor.handle_input("{", window, cx);
 8920        assert_eq!(
 8921            editor.text(cx),
 8922            "
 8923                {{{a}}}
 8924                {{{b}}}
 8925                {{{c}}}
 8926            "
 8927            .unindent()
 8928        );
 8929        assert_eq!(
 8930            editor.selections.display_ranges(cx),
 8931            [
 8932                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8933                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8934                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8935            ]
 8936        );
 8937
 8938        editor.undo(&Undo, window, cx);
 8939        editor.undo(&Undo, window, cx);
 8940        editor.undo(&Undo, window, cx);
 8941        assert_eq!(
 8942            editor.text(cx),
 8943            "
 8944                a
 8945                b
 8946                c
 8947            "
 8948            .unindent()
 8949        );
 8950        assert_eq!(
 8951            editor.selections.display_ranges(cx),
 8952            [
 8953                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8954                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8955                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8956            ]
 8957        );
 8958
 8959        // Ensure inserting the first character of a multi-byte bracket pair
 8960        // doesn't surround the selections with the bracket.
 8961        editor.handle_input("/", window, cx);
 8962        assert_eq!(
 8963            editor.text(cx),
 8964            "
 8965                /
 8966                /
 8967                /
 8968            "
 8969            .unindent()
 8970        );
 8971        assert_eq!(
 8972            editor.selections.display_ranges(cx),
 8973            [
 8974                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8975                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8976                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8977            ]
 8978        );
 8979
 8980        editor.undo(&Undo, window, cx);
 8981        assert_eq!(
 8982            editor.text(cx),
 8983            "
 8984                a
 8985                b
 8986                c
 8987            "
 8988            .unindent()
 8989        );
 8990        assert_eq!(
 8991            editor.selections.display_ranges(cx),
 8992            [
 8993                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8994                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8995                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8996            ]
 8997        );
 8998
 8999        // Ensure inserting the last character of a multi-byte bracket pair
 9000        // doesn't surround the selections with the bracket.
 9001        editor.handle_input("*", window, cx);
 9002        assert_eq!(
 9003            editor.text(cx),
 9004            "
 9005                *
 9006                *
 9007                *
 9008            "
 9009            .unindent()
 9010        );
 9011        assert_eq!(
 9012            editor.selections.display_ranges(cx),
 9013            [
 9014                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9015                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9016                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9017            ]
 9018        );
 9019    });
 9020}
 9021
 9022#[gpui::test]
 9023async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9024    init_test(cx, |_| {});
 9025
 9026    let language = Arc::new(Language::new(
 9027        LanguageConfig {
 9028            brackets: BracketPairConfig {
 9029                pairs: vec![BracketPair {
 9030                    start: "{".to_string(),
 9031                    end: "}".to_string(),
 9032                    close: true,
 9033                    surround: true,
 9034                    newline: true,
 9035                }],
 9036                ..Default::default()
 9037            },
 9038            autoclose_before: "}".to_string(),
 9039            ..Default::default()
 9040        },
 9041        Some(tree_sitter_rust::LANGUAGE.into()),
 9042    ));
 9043
 9044    let text = r#"
 9045        a
 9046        b
 9047        c
 9048    "#
 9049    .unindent();
 9050
 9051    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9052    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9053    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9054    editor
 9055        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9056        .await;
 9057
 9058    editor.update_in(cx, |editor, window, cx| {
 9059        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9060            s.select_ranges([
 9061                Point::new(0, 1)..Point::new(0, 1),
 9062                Point::new(1, 1)..Point::new(1, 1),
 9063                Point::new(2, 1)..Point::new(2, 1),
 9064            ])
 9065        });
 9066
 9067        editor.handle_input("{", window, cx);
 9068        editor.handle_input("{", window, cx);
 9069        editor.handle_input("_", window, cx);
 9070        assert_eq!(
 9071            editor.text(cx),
 9072            "
 9073                a{{_}}
 9074                b{{_}}
 9075                c{{_}}
 9076            "
 9077            .unindent()
 9078        );
 9079        assert_eq!(
 9080            editor.selections.ranges::<Point>(cx),
 9081            [
 9082                Point::new(0, 4)..Point::new(0, 4),
 9083                Point::new(1, 4)..Point::new(1, 4),
 9084                Point::new(2, 4)..Point::new(2, 4)
 9085            ]
 9086        );
 9087
 9088        editor.backspace(&Default::default(), window, cx);
 9089        editor.backspace(&Default::default(), window, cx);
 9090        assert_eq!(
 9091            editor.text(cx),
 9092            "
 9093                a{}
 9094                b{}
 9095                c{}
 9096            "
 9097            .unindent()
 9098        );
 9099        assert_eq!(
 9100            editor.selections.ranges::<Point>(cx),
 9101            [
 9102                Point::new(0, 2)..Point::new(0, 2),
 9103                Point::new(1, 2)..Point::new(1, 2),
 9104                Point::new(2, 2)..Point::new(2, 2)
 9105            ]
 9106        );
 9107
 9108        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9109        assert_eq!(
 9110            editor.text(cx),
 9111            "
 9112                a
 9113                b
 9114                c
 9115            "
 9116            .unindent()
 9117        );
 9118        assert_eq!(
 9119            editor.selections.ranges::<Point>(cx),
 9120            [
 9121                Point::new(0, 1)..Point::new(0, 1),
 9122                Point::new(1, 1)..Point::new(1, 1),
 9123                Point::new(2, 1)..Point::new(2, 1)
 9124            ]
 9125        );
 9126    });
 9127}
 9128
 9129#[gpui::test]
 9130async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9131    init_test(cx, |settings| {
 9132        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9133    });
 9134
 9135    let mut cx = EditorTestContext::new(cx).await;
 9136
 9137    let language = Arc::new(Language::new(
 9138        LanguageConfig {
 9139            brackets: BracketPairConfig {
 9140                pairs: vec![
 9141                    BracketPair {
 9142                        start: "{".to_string(),
 9143                        end: "}".to_string(),
 9144                        close: true,
 9145                        surround: true,
 9146                        newline: true,
 9147                    },
 9148                    BracketPair {
 9149                        start: "(".to_string(),
 9150                        end: ")".to_string(),
 9151                        close: true,
 9152                        surround: true,
 9153                        newline: true,
 9154                    },
 9155                    BracketPair {
 9156                        start: "[".to_string(),
 9157                        end: "]".to_string(),
 9158                        close: false,
 9159                        surround: true,
 9160                        newline: true,
 9161                    },
 9162                ],
 9163                ..Default::default()
 9164            },
 9165            autoclose_before: "})]".to_string(),
 9166            ..Default::default()
 9167        },
 9168        Some(tree_sitter_rust::LANGUAGE.into()),
 9169    ));
 9170
 9171    cx.language_registry().add(language.clone());
 9172    cx.update_buffer(|buffer, cx| {
 9173        buffer.set_language(Some(language), cx);
 9174    });
 9175
 9176    cx.set_state(
 9177        &"
 9178            {(ˇ)}
 9179            [[ˇ]]
 9180            {(ˇ)}
 9181        "
 9182        .unindent(),
 9183    );
 9184
 9185    cx.update_editor(|editor, window, cx| {
 9186        editor.backspace(&Default::default(), window, cx);
 9187        editor.backspace(&Default::default(), window, cx);
 9188    });
 9189
 9190    cx.assert_editor_state(
 9191        &"
 9192            ˇ
 9193            ˇ]]
 9194            ˇ
 9195        "
 9196        .unindent(),
 9197    );
 9198
 9199    cx.update_editor(|editor, window, cx| {
 9200        editor.handle_input("{", window, cx);
 9201        editor.handle_input("{", window, cx);
 9202        editor.move_right(&MoveRight, window, cx);
 9203        editor.move_right(&MoveRight, window, cx);
 9204        editor.move_left(&MoveLeft, window, cx);
 9205        editor.move_left(&MoveLeft, window, cx);
 9206        editor.backspace(&Default::default(), window, cx);
 9207    });
 9208
 9209    cx.assert_editor_state(
 9210        &"
 9211            {ˇ}
 9212            {ˇ}]]
 9213            {ˇ}
 9214        "
 9215        .unindent(),
 9216    );
 9217
 9218    cx.update_editor(|editor, window, cx| {
 9219        editor.backspace(&Default::default(), window, cx);
 9220    });
 9221
 9222    cx.assert_editor_state(
 9223        &"
 9224            ˇ
 9225            ˇ]]
 9226            ˇ
 9227        "
 9228        .unindent(),
 9229    );
 9230}
 9231
 9232#[gpui::test]
 9233async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9234    init_test(cx, |_| {});
 9235
 9236    let language = Arc::new(Language::new(
 9237        LanguageConfig::default(),
 9238        Some(tree_sitter_rust::LANGUAGE.into()),
 9239    ));
 9240
 9241    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9242    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9243    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9244    editor
 9245        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9246        .await;
 9247
 9248    editor.update_in(cx, |editor, window, cx| {
 9249        editor.set_auto_replace_emoji_shortcode(true);
 9250
 9251        editor.handle_input("Hello ", window, cx);
 9252        editor.handle_input(":wave", window, cx);
 9253        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9254
 9255        editor.handle_input(":", window, cx);
 9256        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9257
 9258        editor.handle_input(" :smile", window, cx);
 9259        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9260
 9261        editor.handle_input(":", window, cx);
 9262        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9263
 9264        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9265        editor.handle_input(":wave", window, cx);
 9266        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9267
 9268        editor.handle_input(":", window, cx);
 9269        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9270
 9271        editor.handle_input(":1", window, cx);
 9272        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9273
 9274        editor.handle_input(":", window, cx);
 9275        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9276
 9277        // Ensure shortcode does not get replaced when it is part of a word
 9278        editor.handle_input(" Test:wave", window, cx);
 9279        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9280
 9281        editor.handle_input(":", window, cx);
 9282        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9283
 9284        editor.set_auto_replace_emoji_shortcode(false);
 9285
 9286        // Ensure shortcode does not get replaced when auto replace is off
 9287        editor.handle_input(" :wave", window, cx);
 9288        assert_eq!(
 9289            editor.text(cx),
 9290            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9291        );
 9292
 9293        editor.handle_input(":", window, cx);
 9294        assert_eq!(
 9295            editor.text(cx),
 9296            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9297        );
 9298    });
 9299}
 9300
 9301#[gpui::test]
 9302async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9303    init_test(cx, |_| {});
 9304
 9305    let (text, insertion_ranges) = marked_text_ranges(
 9306        indoc! {"
 9307            ˇ
 9308        "},
 9309        false,
 9310    );
 9311
 9312    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9313    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9314
 9315    _ = editor.update_in(cx, |editor, window, cx| {
 9316        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9317
 9318        editor
 9319            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9320            .unwrap();
 9321
 9322        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9323            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9324            assert_eq!(editor.text(cx), expected_text);
 9325            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9326        }
 9327
 9328        assert(
 9329            editor,
 9330            cx,
 9331            indoc! {"
 9332            type «» =•
 9333            "},
 9334        );
 9335
 9336        assert!(editor.context_menu_visible(), "There should be a matches");
 9337    });
 9338}
 9339
 9340#[gpui::test]
 9341async fn test_snippets(cx: &mut TestAppContext) {
 9342    init_test(cx, |_| {});
 9343
 9344    let mut cx = EditorTestContext::new(cx).await;
 9345
 9346    cx.set_state(indoc! {"
 9347        a.ˇ b
 9348        a.ˇ b
 9349        a.ˇ b
 9350    "});
 9351
 9352    cx.update_editor(|editor, window, cx| {
 9353        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9354        let insertion_ranges = editor
 9355            .selections
 9356            .all(cx)
 9357            .iter()
 9358            .map(|s| s.range().clone())
 9359            .collect::<Vec<_>>();
 9360        editor
 9361            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9362            .unwrap();
 9363    });
 9364
 9365    cx.assert_editor_state(indoc! {"
 9366        a.f(«oneˇ», two, «threeˇ») b
 9367        a.f(«oneˇ», two, «threeˇ») b
 9368        a.f(«oneˇ», two, «threeˇ») b
 9369    "});
 9370
 9371    // Can't move earlier than the first tab stop
 9372    cx.update_editor(|editor, window, cx| {
 9373        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9374    });
 9375    cx.assert_editor_state(indoc! {"
 9376        a.f(«oneˇ», two, «threeˇ») b
 9377        a.f(«oneˇ», two, «threeˇ») b
 9378        a.f(«oneˇ», two, «threeˇ») b
 9379    "});
 9380
 9381    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9382    cx.assert_editor_state(indoc! {"
 9383        a.f(one, «twoˇ», three) b
 9384        a.f(one, «twoˇ», three) b
 9385        a.f(one, «twoˇ», three) b
 9386    "});
 9387
 9388    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9389    cx.assert_editor_state(indoc! {"
 9390        a.f(«oneˇ», two, «threeˇ») b
 9391        a.f(«oneˇ», two, «threeˇ») b
 9392        a.f(«oneˇ», two, «threeˇ») b
 9393    "});
 9394
 9395    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9396    cx.assert_editor_state(indoc! {"
 9397        a.f(one, «twoˇ», three) b
 9398        a.f(one, «twoˇ», three) b
 9399        a.f(one, «twoˇ», three) b
 9400    "});
 9401    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9402    cx.assert_editor_state(indoc! {"
 9403        a.f(one, two, three)ˇ b
 9404        a.f(one, two, three)ˇ b
 9405        a.f(one, two, three)ˇ b
 9406    "});
 9407
 9408    // As soon as the last tab stop is reached, snippet state is gone
 9409    cx.update_editor(|editor, window, cx| {
 9410        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9411    });
 9412    cx.assert_editor_state(indoc! {"
 9413        a.f(one, two, three)ˇ b
 9414        a.f(one, two, three)ˇ b
 9415        a.f(one, two, three)ˇ b
 9416    "});
 9417}
 9418
 9419#[gpui::test]
 9420async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9421    init_test(cx, |_| {});
 9422
 9423    let mut cx = EditorTestContext::new(cx).await;
 9424
 9425    cx.update_editor(|editor, window, cx| {
 9426        let snippet = Snippet::parse(indoc! {"
 9427            /*
 9428             * Multiline comment with leading indentation
 9429             *
 9430             * $1
 9431             */
 9432            $0"})
 9433        .unwrap();
 9434        let insertion_ranges = editor
 9435            .selections
 9436            .all(cx)
 9437            .iter()
 9438            .map(|s| s.range().clone())
 9439            .collect::<Vec<_>>();
 9440        editor
 9441            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9442            .unwrap();
 9443    });
 9444
 9445    cx.assert_editor_state(indoc! {"
 9446        /*
 9447         * Multiline comment with leading indentation
 9448         *
 9449         * ˇ
 9450         */
 9451    "});
 9452
 9453    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9454    cx.assert_editor_state(indoc! {"
 9455        /*
 9456         * Multiline comment with leading indentation
 9457         *
 9458         *•
 9459         */
 9460        ˇ"});
 9461}
 9462
 9463#[gpui::test]
 9464async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9465    init_test(cx, |_| {});
 9466
 9467    let fs = FakeFs::new(cx.executor());
 9468    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9469
 9470    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9471
 9472    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9473    language_registry.add(rust_lang());
 9474    let mut fake_servers = language_registry.register_fake_lsp(
 9475        "Rust",
 9476        FakeLspAdapter {
 9477            capabilities: lsp::ServerCapabilities {
 9478                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9479                ..Default::default()
 9480            },
 9481            ..Default::default()
 9482        },
 9483    );
 9484
 9485    let buffer = project
 9486        .update(cx, |project, cx| {
 9487            project.open_local_buffer(path!("/file.rs"), cx)
 9488        })
 9489        .await
 9490        .unwrap();
 9491
 9492    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9493    let (editor, cx) = cx.add_window_view(|window, cx| {
 9494        build_editor_with_project(project.clone(), buffer, window, cx)
 9495    });
 9496    editor.update_in(cx, |editor, window, cx| {
 9497        editor.set_text("one\ntwo\nthree\n", window, cx)
 9498    });
 9499    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9500
 9501    cx.executor().start_waiting();
 9502    let fake_server = fake_servers.next().await.unwrap();
 9503
 9504    {
 9505        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9506            move |params, _| async move {
 9507                assert_eq!(
 9508                    params.text_document.uri,
 9509                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9510                );
 9511                assert_eq!(params.options.tab_size, 4);
 9512                Ok(Some(vec![lsp::TextEdit::new(
 9513                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9514                    ", ".to_string(),
 9515                )]))
 9516            },
 9517        );
 9518        let save = editor
 9519            .update_in(cx, |editor, window, cx| {
 9520                editor.save(
 9521                    SaveOptions {
 9522                        format: true,
 9523                        autosave: false,
 9524                    },
 9525                    project.clone(),
 9526                    window,
 9527                    cx,
 9528                )
 9529            })
 9530            .unwrap();
 9531        cx.executor().start_waiting();
 9532        save.await;
 9533
 9534        assert_eq!(
 9535            editor.update(cx, |editor, cx| editor.text(cx)),
 9536            "one, two\nthree\n"
 9537        );
 9538        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9539    }
 9540
 9541    {
 9542        editor.update_in(cx, |editor, window, cx| {
 9543            editor.set_text("one\ntwo\nthree\n", window, cx)
 9544        });
 9545        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9546
 9547        // Ensure we can still save even if formatting hangs.
 9548        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9549            move |params, _| async move {
 9550                assert_eq!(
 9551                    params.text_document.uri,
 9552                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9553                );
 9554                futures::future::pending::<()>().await;
 9555                unreachable!()
 9556            },
 9557        );
 9558        let save = editor
 9559            .update_in(cx, |editor, window, cx| {
 9560                editor.save(
 9561                    SaveOptions {
 9562                        format: true,
 9563                        autosave: false,
 9564                    },
 9565                    project.clone(),
 9566                    window,
 9567                    cx,
 9568                )
 9569            })
 9570            .unwrap();
 9571        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9572        cx.executor().start_waiting();
 9573        save.await;
 9574        assert_eq!(
 9575            editor.update(cx, |editor, cx| editor.text(cx)),
 9576            "one\ntwo\nthree\n"
 9577        );
 9578    }
 9579
 9580    // Set rust language override and assert overridden tabsize is sent to language server
 9581    update_test_language_settings(cx, |settings| {
 9582        settings.languages.0.insert(
 9583            "Rust".into(),
 9584            LanguageSettingsContent {
 9585                tab_size: NonZeroU32::new(8),
 9586                ..Default::default()
 9587            },
 9588        );
 9589    });
 9590
 9591    {
 9592        editor.update_in(cx, |editor, window, cx| {
 9593            editor.set_text("somehting_new\n", window, cx)
 9594        });
 9595        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9596        let _formatting_request_signal = fake_server
 9597            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9598                assert_eq!(
 9599                    params.text_document.uri,
 9600                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9601                );
 9602                assert_eq!(params.options.tab_size, 8);
 9603                Ok(Some(vec![]))
 9604            });
 9605        let save = editor
 9606            .update_in(cx, |editor, window, cx| {
 9607                editor.save(
 9608                    SaveOptions {
 9609                        format: true,
 9610                        autosave: false,
 9611                    },
 9612                    project.clone(),
 9613                    window,
 9614                    cx,
 9615                )
 9616            })
 9617            .unwrap();
 9618        cx.executor().start_waiting();
 9619        save.await;
 9620    }
 9621}
 9622
 9623#[gpui::test]
 9624async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9625    init_test(cx, |settings| {
 9626        settings.defaults.ensure_final_newline_on_save = Some(false);
 9627    });
 9628
 9629    let fs = FakeFs::new(cx.executor());
 9630    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9631
 9632    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9633
 9634    let buffer = project
 9635        .update(cx, |project, cx| {
 9636            project.open_local_buffer(path!("/file.txt"), cx)
 9637        })
 9638        .await
 9639        .unwrap();
 9640
 9641    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9642    let (editor, cx) = cx.add_window_view(|window, cx| {
 9643        build_editor_with_project(project.clone(), buffer, window, cx)
 9644    });
 9645    editor.update_in(cx, |editor, window, cx| {
 9646        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9647            s.select_ranges([0..0])
 9648        });
 9649    });
 9650    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9651
 9652    editor.update_in(cx, |editor, window, cx| {
 9653        editor.handle_input("\n", window, cx)
 9654    });
 9655    cx.run_until_parked();
 9656    save(&editor, &project, cx).await;
 9657    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9658
 9659    editor.update_in(cx, |editor, window, cx| {
 9660        editor.undo(&Default::default(), window, cx);
 9661    });
 9662    save(&editor, &project, cx).await;
 9663    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9664
 9665    editor.update_in(cx, |editor, window, cx| {
 9666        editor.redo(&Default::default(), window, cx);
 9667    });
 9668    cx.run_until_parked();
 9669    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9670
 9671    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9672        let save = editor
 9673            .update_in(cx, |editor, window, cx| {
 9674                editor.save(
 9675                    SaveOptions {
 9676                        format: true,
 9677                        autosave: false,
 9678                    },
 9679                    project.clone(),
 9680                    window,
 9681                    cx,
 9682                )
 9683            })
 9684            .unwrap();
 9685        cx.executor().start_waiting();
 9686        save.await;
 9687        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9688    }
 9689}
 9690
 9691#[gpui::test]
 9692async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9693    init_test(cx, |_| {});
 9694
 9695    let cols = 4;
 9696    let rows = 10;
 9697    let sample_text_1 = sample_text(rows, cols, 'a');
 9698    assert_eq!(
 9699        sample_text_1,
 9700        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9701    );
 9702    let sample_text_2 = sample_text(rows, cols, 'l');
 9703    assert_eq!(
 9704        sample_text_2,
 9705        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9706    );
 9707    let sample_text_3 = sample_text(rows, cols, 'v');
 9708    assert_eq!(
 9709        sample_text_3,
 9710        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9711    );
 9712
 9713    let fs = FakeFs::new(cx.executor());
 9714    fs.insert_tree(
 9715        path!("/a"),
 9716        json!({
 9717            "main.rs": sample_text_1,
 9718            "other.rs": sample_text_2,
 9719            "lib.rs": sample_text_3,
 9720        }),
 9721    )
 9722    .await;
 9723
 9724    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9725    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9726    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9727
 9728    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9729    language_registry.add(rust_lang());
 9730    let mut fake_servers = language_registry.register_fake_lsp(
 9731        "Rust",
 9732        FakeLspAdapter {
 9733            capabilities: lsp::ServerCapabilities {
 9734                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9735                ..Default::default()
 9736            },
 9737            ..Default::default()
 9738        },
 9739    );
 9740
 9741    let worktree = project.update(cx, |project, cx| {
 9742        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9743        assert_eq!(worktrees.len(), 1);
 9744        worktrees.pop().unwrap()
 9745    });
 9746    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9747
 9748    let buffer_1 = project
 9749        .update(cx, |project, cx| {
 9750            project.open_buffer((worktree_id, "main.rs"), cx)
 9751        })
 9752        .await
 9753        .unwrap();
 9754    let buffer_2 = project
 9755        .update(cx, |project, cx| {
 9756            project.open_buffer((worktree_id, "other.rs"), cx)
 9757        })
 9758        .await
 9759        .unwrap();
 9760    let buffer_3 = project
 9761        .update(cx, |project, cx| {
 9762            project.open_buffer((worktree_id, "lib.rs"), cx)
 9763        })
 9764        .await
 9765        .unwrap();
 9766
 9767    let multi_buffer = cx.new(|cx| {
 9768        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9769        multi_buffer.push_excerpts(
 9770            buffer_1.clone(),
 9771            [
 9772                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9773                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9774                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9775            ],
 9776            cx,
 9777        );
 9778        multi_buffer.push_excerpts(
 9779            buffer_2.clone(),
 9780            [
 9781                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9782                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9783                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9784            ],
 9785            cx,
 9786        );
 9787        multi_buffer.push_excerpts(
 9788            buffer_3.clone(),
 9789            [
 9790                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9791                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9792                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9793            ],
 9794            cx,
 9795        );
 9796        multi_buffer
 9797    });
 9798    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9799        Editor::new(
 9800            EditorMode::full(),
 9801            multi_buffer,
 9802            Some(project.clone()),
 9803            window,
 9804            cx,
 9805        )
 9806    });
 9807
 9808    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9809        editor.change_selections(
 9810            SelectionEffects::scroll(Autoscroll::Next),
 9811            window,
 9812            cx,
 9813            |s| s.select_ranges(Some(1..2)),
 9814        );
 9815        editor.insert("|one|two|three|", window, cx);
 9816    });
 9817    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9818    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9819        editor.change_selections(
 9820            SelectionEffects::scroll(Autoscroll::Next),
 9821            window,
 9822            cx,
 9823            |s| s.select_ranges(Some(60..70)),
 9824        );
 9825        editor.insert("|four|five|six|", window, cx);
 9826    });
 9827    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9828
 9829    // First two buffers should be edited, but not the third one.
 9830    assert_eq!(
 9831        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9832        "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}",
 9833    );
 9834    buffer_1.update(cx, |buffer, _| {
 9835        assert!(buffer.is_dirty());
 9836        assert_eq!(
 9837            buffer.text(),
 9838            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9839        )
 9840    });
 9841    buffer_2.update(cx, |buffer, _| {
 9842        assert!(buffer.is_dirty());
 9843        assert_eq!(
 9844            buffer.text(),
 9845            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9846        )
 9847    });
 9848    buffer_3.update(cx, |buffer, _| {
 9849        assert!(!buffer.is_dirty());
 9850        assert_eq!(buffer.text(), sample_text_3,)
 9851    });
 9852    cx.executor().run_until_parked();
 9853
 9854    cx.executor().start_waiting();
 9855    let save = multi_buffer_editor
 9856        .update_in(cx, |editor, window, cx| {
 9857            editor.save(
 9858                SaveOptions {
 9859                    format: true,
 9860                    autosave: false,
 9861                },
 9862                project.clone(),
 9863                window,
 9864                cx,
 9865            )
 9866        })
 9867        .unwrap();
 9868
 9869    let fake_server = fake_servers.next().await.unwrap();
 9870    fake_server
 9871        .server
 9872        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9873            Ok(Some(vec![lsp::TextEdit::new(
 9874                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9875                format!("[{} formatted]", params.text_document.uri),
 9876            )]))
 9877        })
 9878        .detach();
 9879    save.await;
 9880
 9881    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9882    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9883    assert_eq!(
 9884        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9885        uri!(
 9886            "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}"
 9887        ),
 9888    );
 9889    buffer_1.update(cx, |buffer, _| {
 9890        assert!(!buffer.is_dirty());
 9891        assert_eq!(
 9892            buffer.text(),
 9893            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9894        )
 9895    });
 9896    buffer_2.update(cx, |buffer, _| {
 9897        assert!(!buffer.is_dirty());
 9898        assert_eq!(
 9899            buffer.text(),
 9900            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9901        )
 9902    });
 9903    buffer_3.update(cx, |buffer, _| {
 9904        assert!(!buffer.is_dirty());
 9905        assert_eq!(buffer.text(), sample_text_3,)
 9906    });
 9907}
 9908
 9909#[gpui::test]
 9910async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9911    init_test(cx, |_| {});
 9912
 9913    let fs = FakeFs::new(cx.executor());
 9914    fs.insert_tree(
 9915        path!("/dir"),
 9916        json!({
 9917            "file1.rs": "fn main() { println!(\"hello\"); }",
 9918            "file2.rs": "fn test() { println!(\"test\"); }",
 9919            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9920        }),
 9921    )
 9922    .await;
 9923
 9924    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9925    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9926    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9927
 9928    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9929    language_registry.add(rust_lang());
 9930
 9931    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9932    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9933
 9934    // Open three buffers
 9935    let buffer_1 = project
 9936        .update(cx, |project, cx| {
 9937            project.open_buffer((worktree_id, "file1.rs"), cx)
 9938        })
 9939        .await
 9940        .unwrap();
 9941    let buffer_2 = project
 9942        .update(cx, |project, cx| {
 9943            project.open_buffer((worktree_id, "file2.rs"), cx)
 9944        })
 9945        .await
 9946        .unwrap();
 9947    let buffer_3 = project
 9948        .update(cx, |project, cx| {
 9949            project.open_buffer((worktree_id, "file3.rs"), cx)
 9950        })
 9951        .await
 9952        .unwrap();
 9953
 9954    // Create a multi-buffer with all three buffers
 9955    let multi_buffer = cx.new(|cx| {
 9956        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9957        multi_buffer.push_excerpts(
 9958            buffer_1.clone(),
 9959            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9960            cx,
 9961        );
 9962        multi_buffer.push_excerpts(
 9963            buffer_2.clone(),
 9964            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9965            cx,
 9966        );
 9967        multi_buffer.push_excerpts(
 9968            buffer_3.clone(),
 9969            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9970            cx,
 9971        );
 9972        multi_buffer
 9973    });
 9974
 9975    let editor = cx.new_window_entity(|window, cx| {
 9976        Editor::new(
 9977            EditorMode::full(),
 9978            multi_buffer,
 9979            Some(project.clone()),
 9980            window,
 9981            cx,
 9982        )
 9983    });
 9984
 9985    // Edit only the first buffer
 9986    editor.update_in(cx, |editor, window, cx| {
 9987        editor.change_selections(
 9988            SelectionEffects::scroll(Autoscroll::Next),
 9989            window,
 9990            cx,
 9991            |s| s.select_ranges(Some(10..10)),
 9992        );
 9993        editor.insert("// edited", window, cx);
 9994    });
 9995
 9996    // Verify that only buffer 1 is dirty
 9997    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9998    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9999    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10000
10001    // Get write counts after file creation (files were created with initial content)
10002    // We expect each file to have been written once during creation
10003    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10004    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10005    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10006
10007    // Perform autosave
10008    let save_task = editor.update_in(cx, |editor, window, cx| {
10009        editor.save(
10010            SaveOptions {
10011                format: true,
10012                autosave: true,
10013            },
10014            project.clone(),
10015            window,
10016            cx,
10017        )
10018    });
10019    save_task.await.unwrap();
10020
10021    // Only the dirty buffer should have been saved
10022    assert_eq!(
10023        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10024        1,
10025        "Buffer 1 was dirty, so it should have been written once during autosave"
10026    );
10027    assert_eq!(
10028        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10029        0,
10030        "Buffer 2 was clean, so it should not have been written during autosave"
10031    );
10032    assert_eq!(
10033        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10034        0,
10035        "Buffer 3 was clean, so it should not have been written during autosave"
10036    );
10037
10038    // Verify buffer states after autosave
10039    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10040    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10041    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10042
10043    // Now perform a manual save (format = true)
10044    let save_task = editor.update_in(cx, |editor, window, cx| {
10045        editor.save(
10046            SaveOptions {
10047                format: true,
10048                autosave: false,
10049            },
10050            project.clone(),
10051            window,
10052            cx,
10053        )
10054    });
10055    save_task.await.unwrap();
10056
10057    // During manual save, clean buffers don't get written to disk
10058    // They just get did_save called for language server notifications
10059    assert_eq!(
10060        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10061        1,
10062        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10063    );
10064    assert_eq!(
10065        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10066        0,
10067        "Buffer 2 should not have been written at all"
10068    );
10069    assert_eq!(
10070        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10071        0,
10072        "Buffer 3 should not have been written at all"
10073    );
10074}
10075
10076async fn setup_range_format_test(
10077    cx: &mut TestAppContext,
10078) -> (
10079    Entity<Project>,
10080    Entity<Editor>,
10081    &mut gpui::VisualTestContext,
10082    lsp::FakeLanguageServer,
10083) {
10084    init_test(cx, |_| {});
10085
10086    let fs = FakeFs::new(cx.executor());
10087    fs.insert_file(path!("/file.rs"), Default::default()).await;
10088
10089    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10090
10091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10092    language_registry.add(rust_lang());
10093    let mut fake_servers = language_registry.register_fake_lsp(
10094        "Rust",
10095        FakeLspAdapter {
10096            capabilities: lsp::ServerCapabilities {
10097                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10098                ..lsp::ServerCapabilities::default()
10099            },
10100            ..FakeLspAdapter::default()
10101        },
10102    );
10103
10104    let buffer = project
10105        .update(cx, |project, cx| {
10106            project.open_local_buffer(path!("/file.rs"), cx)
10107        })
10108        .await
10109        .unwrap();
10110
10111    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10112    let (editor, cx) = cx.add_window_view(|window, cx| {
10113        build_editor_with_project(project.clone(), buffer, window, cx)
10114    });
10115
10116    cx.executor().start_waiting();
10117    let fake_server = fake_servers.next().await.unwrap();
10118
10119    (project, editor, cx, fake_server)
10120}
10121
10122#[gpui::test]
10123async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10124    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10125
10126    editor.update_in(cx, |editor, window, cx| {
10127        editor.set_text("one\ntwo\nthree\n", window, cx)
10128    });
10129    assert!(cx.read(|cx| editor.is_dirty(cx)));
10130
10131    let save = editor
10132        .update_in(cx, |editor, window, cx| {
10133            editor.save(
10134                SaveOptions {
10135                    format: true,
10136                    autosave: false,
10137                },
10138                project.clone(),
10139                window,
10140                cx,
10141            )
10142        })
10143        .unwrap();
10144    fake_server
10145        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10146            assert_eq!(
10147                params.text_document.uri,
10148                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10149            );
10150            assert_eq!(params.options.tab_size, 4);
10151            Ok(Some(vec![lsp::TextEdit::new(
10152                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10153                ", ".to_string(),
10154            )]))
10155        })
10156        .next()
10157        .await;
10158    cx.executor().start_waiting();
10159    save.await;
10160    assert_eq!(
10161        editor.update(cx, |editor, cx| editor.text(cx)),
10162        "one, two\nthree\n"
10163    );
10164    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10165}
10166
10167#[gpui::test]
10168async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10169    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10170
10171    editor.update_in(cx, |editor, window, cx| {
10172        editor.set_text("one\ntwo\nthree\n", window, cx)
10173    });
10174    assert!(cx.read(|cx| editor.is_dirty(cx)));
10175
10176    // Test that save still works when formatting hangs
10177    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10178        move |params, _| async move {
10179            assert_eq!(
10180                params.text_document.uri,
10181                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10182            );
10183            futures::future::pending::<()>().await;
10184            unreachable!()
10185        },
10186    );
10187    let save = editor
10188        .update_in(cx, |editor, window, cx| {
10189            editor.save(
10190                SaveOptions {
10191                    format: true,
10192                    autosave: false,
10193                },
10194                project.clone(),
10195                window,
10196                cx,
10197            )
10198        })
10199        .unwrap();
10200    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10201    cx.executor().start_waiting();
10202    save.await;
10203    assert_eq!(
10204        editor.update(cx, |editor, cx| editor.text(cx)),
10205        "one\ntwo\nthree\n"
10206    );
10207    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10208}
10209
10210#[gpui::test]
10211async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10212    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10213
10214    // Buffer starts clean, no formatting should be requested
10215    let save = editor
10216        .update_in(cx, |editor, window, cx| {
10217            editor.save(
10218                SaveOptions {
10219                    format: false,
10220                    autosave: false,
10221                },
10222                project.clone(),
10223                window,
10224                cx,
10225            )
10226        })
10227        .unwrap();
10228    let _pending_format_request = fake_server
10229        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10230            panic!("Should not be invoked");
10231        })
10232        .next();
10233    cx.executor().start_waiting();
10234    save.await;
10235    cx.run_until_parked();
10236}
10237
10238#[gpui::test]
10239async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10240    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10241
10242    // Set Rust language override and assert overridden tabsize is sent to language server
10243    update_test_language_settings(cx, |settings| {
10244        settings.languages.0.insert(
10245            "Rust".into(),
10246            LanguageSettingsContent {
10247                tab_size: NonZeroU32::new(8),
10248                ..Default::default()
10249            },
10250        );
10251    });
10252
10253    editor.update_in(cx, |editor, window, cx| {
10254        editor.set_text("something_new\n", window, cx)
10255    });
10256    assert!(cx.read(|cx| editor.is_dirty(cx)));
10257    let save = editor
10258        .update_in(cx, |editor, window, cx| {
10259            editor.save(
10260                SaveOptions {
10261                    format: true,
10262                    autosave: false,
10263                },
10264                project.clone(),
10265                window,
10266                cx,
10267            )
10268        })
10269        .unwrap();
10270    fake_server
10271        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10272            assert_eq!(
10273                params.text_document.uri,
10274                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10275            );
10276            assert_eq!(params.options.tab_size, 8);
10277            Ok(Some(Vec::new()))
10278        })
10279        .next()
10280        .await;
10281    save.await;
10282}
10283
10284#[gpui::test]
10285async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10286    init_test(cx, |settings| {
10287        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10288            Formatter::LanguageServer { name: None },
10289        )))
10290    });
10291
10292    let fs = FakeFs::new(cx.executor());
10293    fs.insert_file(path!("/file.rs"), Default::default()).await;
10294
10295    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10296
10297    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10298    language_registry.add(Arc::new(Language::new(
10299        LanguageConfig {
10300            name: "Rust".into(),
10301            matcher: LanguageMatcher {
10302                path_suffixes: vec!["rs".to_string()],
10303                ..Default::default()
10304            },
10305            ..LanguageConfig::default()
10306        },
10307        Some(tree_sitter_rust::LANGUAGE.into()),
10308    )));
10309    update_test_language_settings(cx, |settings| {
10310        // Enable Prettier formatting for the same buffer, and ensure
10311        // LSP is called instead of Prettier.
10312        settings.defaults.prettier = Some(PrettierSettings {
10313            allowed: true,
10314            ..PrettierSettings::default()
10315        });
10316    });
10317    let mut fake_servers = language_registry.register_fake_lsp(
10318        "Rust",
10319        FakeLspAdapter {
10320            capabilities: lsp::ServerCapabilities {
10321                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10322                ..Default::default()
10323            },
10324            ..Default::default()
10325        },
10326    );
10327
10328    let buffer = project
10329        .update(cx, |project, cx| {
10330            project.open_local_buffer(path!("/file.rs"), cx)
10331        })
10332        .await
10333        .unwrap();
10334
10335    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10336    let (editor, cx) = cx.add_window_view(|window, cx| {
10337        build_editor_with_project(project.clone(), buffer, window, cx)
10338    });
10339    editor.update_in(cx, |editor, window, cx| {
10340        editor.set_text("one\ntwo\nthree\n", window, cx)
10341    });
10342
10343    cx.executor().start_waiting();
10344    let fake_server = fake_servers.next().await.unwrap();
10345
10346    let format = editor
10347        .update_in(cx, |editor, window, cx| {
10348            editor.perform_format(
10349                project.clone(),
10350                FormatTrigger::Manual,
10351                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10352                window,
10353                cx,
10354            )
10355        })
10356        .unwrap();
10357    fake_server
10358        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10359            assert_eq!(
10360                params.text_document.uri,
10361                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10362            );
10363            assert_eq!(params.options.tab_size, 4);
10364            Ok(Some(vec![lsp::TextEdit::new(
10365                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10366                ", ".to_string(),
10367            )]))
10368        })
10369        .next()
10370        .await;
10371    cx.executor().start_waiting();
10372    format.await;
10373    assert_eq!(
10374        editor.update(cx, |editor, cx| editor.text(cx)),
10375        "one, two\nthree\n"
10376    );
10377
10378    editor.update_in(cx, |editor, window, cx| {
10379        editor.set_text("one\ntwo\nthree\n", window, cx)
10380    });
10381    // Ensure we don't lock if formatting hangs.
10382    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10383        move |params, _| async move {
10384            assert_eq!(
10385                params.text_document.uri,
10386                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10387            );
10388            futures::future::pending::<()>().await;
10389            unreachable!()
10390        },
10391    );
10392    let format = editor
10393        .update_in(cx, |editor, window, cx| {
10394            editor.perform_format(
10395                project,
10396                FormatTrigger::Manual,
10397                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10398                window,
10399                cx,
10400            )
10401        })
10402        .unwrap();
10403    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10404    cx.executor().start_waiting();
10405    format.await;
10406    assert_eq!(
10407        editor.update(cx, |editor, cx| editor.text(cx)),
10408        "one\ntwo\nthree\n"
10409    );
10410}
10411
10412#[gpui::test]
10413async fn test_multiple_formatters(cx: &mut TestAppContext) {
10414    init_test(cx, |settings| {
10415        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10416        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10417            Formatter::LanguageServer { name: None },
10418            Formatter::CodeActions(
10419                [
10420                    ("code-action-1".into(), true),
10421                    ("code-action-2".into(), true),
10422                ]
10423                .into_iter()
10424                .collect(),
10425            ),
10426        ])))
10427    });
10428
10429    let fs = FakeFs::new(cx.executor());
10430    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10431        .await;
10432
10433    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10434    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10435    language_registry.add(rust_lang());
10436
10437    let mut fake_servers = language_registry.register_fake_lsp(
10438        "Rust",
10439        FakeLspAdapter {
10440            capabilities: lsp::ServerCapabilities {
10441                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10442                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10443                    commands: vec!["the-command-for-code-action-1".into()],
10444                    ..Default::default()
10445                }),
10446                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10447                ..Default::default()
10448            },
10449            ..Default::default()
10450        },
10451    );
10452
10453    let buffer = project
10454        .update(cx, |project, cx| {
10455            project.open_local_buffer(path!("/file.rs"), cx)
10456        })
10457        .await
10458        .unwrap();
10459
10460    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10461    let (editor, cx) = cx.add_window_view(|window, cx| {
10462        build_editor_with_project(project.clone(), buffer, window, cx)
10463    });
10464
10465    cx.executor().start_waiting();
10466
10467    let fake_server = fake_servers.next().await.unwrap();
10468    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10469        move |_params, _| async move {
10470            Ok(Some(vec![lsp::TextEdit::new(
10471                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10472                "applied-formatting\n".to_string(),
10473            )]))
10474        },
10475    );
10476    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10477        move |params, _| async move {
10478            assert_eq!(
10479                params.context.only,
10480                Some(vec!["code-action-1".into(), "code-action-2".into()])
10481            );
10482            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10483            Ok(Some(vec![
10484                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10485                    kind: Some("code-action-1".into()),
10486                    edit: Some(lsp::WorkspaceEdit::new(
10487                        [(
10488                            uri.clone(),
10489                            vec![lsp::TextEdit::new(
10490                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10491                                "applied-code-action-1-edit\n".to_string(),
10492                            )],
10493                        )]
10494                        .into_iter()
10495                        .collect(),
10496                    )),
10497                    command: Some(lsp::Command {
10498                        command: "the-command-for-code-action-1".into(),
10499                        ..Default::default()
10500                    }),
10501                    ..Default::default()
10502                }),
10503                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10504                    kind: Some("code-action-2".into()),
10505                    edit: Some(lsp::WorkspaceEdit::new(
10506                        [(
10507                            uri.clone(),
10508                            vec![lsp::TextEdit::new(
10509                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10510                                "applied-code-action-2-edit\n".to_string(),
10511                            )],
10512                        )]
10513                        .into_iter()
10514                        .collect(),
10515                    )),
10516                    ..Default::default()
10517                }),
10518            ]))
10519        },
10520    );
10521
10522    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10523        move |params, _| async move { Ok(params) }
10524    });
10525
10526    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10527    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10528        let fake = fake_server.clone();
10529        let lock = command_lock.clone();
10530        move |params, _| {
10531            assert_eq!(params.command, "the-command-for-code-action-1");
10532            let fake = fake.clone();
10533            let lock = lock.clone();
10534            async move {
10535                lock.lock().await;
10536                fake.server
10537                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10538                        label: None,
10539                        edit: lsp::WorkspaceEdit {
10540                            changes: Some(
10541                                [(
10542                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10543                                    vec![lsp::TextEdit {
10544                                        range: lsp::Range::new(
10545                                            lsp::Position::new(0, 0),
10546                                            lsp::Position::new(0, 0),
10547                                        ),
10548                                        new_text: "applied-code-action-1-command\n".into(),
10549                                    }],
10550                                )]
10551                                .into_iter()
10552                                .collect(),
10553                            ),
10554                            ..Default::default()
10555                        },
10556                    })
10557                    .await
10558                    .into_response()
10559                    .unwrap();
10560                Ok(Some(json!(null)))
10561            }
10562        }
10563    });
10564
10565    cx.executor().start_waiting();
10566    editor
10567        .update_in(cx, |editor, window, cx| {
10568            editor.perform_format(
10569                project.clone(),
10570                FormatTrigger::Manual,
10571                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10572                window,
10573                cx,
10574            )
10575        })
10576        .unwrap()
10577        .await;
10578    editor.update(cx, |editor, cx| {
10579        assert_eq!(
10580            editor.text(cx),
10581            r#"
10582                applied-code-action-2-edit
10583                applied-code-action-1-command
10584                applied-code-action-1-edit
10585                applied-formatting
10586                one
10587                two
10588                three
10589            "#
10590            .unindent()
10591        );
10592    });
10593
10594    editor.update_in(cx, |editor, window, cx| {
10595        editor.undo(&Default::default(), window, cx);
10596        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10597    });
10598
10599    // Perform a manual edit while waiting for an LSP command
10600    // that's being run as part of a formatting code action.
10601    let lock_guard = command_lock.lock().await;
10602    let format = editor
10603        .update_in(cx, |editor, window, cx| {
10604            editor.perform_format(
10605                project.clone(),
10606                FormatTrigger::Manual,
10607                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10608                window,
10609                cx,
10610            )
10611        })
10612        .unwrap();
10613    cx.run_until_parked();
10614    editor.update(cx, |editor, cx| {
10615        assert_eq!(
10616            editor.text(cx),
10617            r#"
10618                applied-code-action-1-edit
10619                applied-formatting
10620                one
10621                two
10622                three
10623            "#
10624            .unindent()
10625        );
10626
10627        editor.buffer.update(cx, |buffer, cx| {
10628            let ix = buffer.len(cx);
10629            buffer.edit([(ix..ix, "edited\n")], None, cx);
10630        });
10631    });
10632
10633    // Allow the LSP command to proceed. Because the buffer was edited,
10634    // the second code action will not be run.
10635    drop(lock_guard);
10636    format.await;
10637    editor.update_in(cx, |editor, window, cx| {
10638        assert_eq!(
10639            editor.text(cx),
10640            r#"
10641                applied-code-action-1-command
10642                applied-code-action-1-edit
10643                applied-formatting
10644                one
10645                two
10646                three
10647                edited
10648            "#
10649            .unindent()
10650        );
10651
10652        // The manual edit is undone first, because it is the last thing the user did
10653        // (even though the command completed afterwards).
10654        editor.undo(&Default::default(), window, cx);
10655        assert_eq!(
10656            editor.text(cx),
10657            r#"
10658                applied-code-action-1-command
10659                applied-code-action-1-edit
10660                applied-formatting
10661                one
10662                two
10663                three
10664            "#
10665            .unindent()
10666        );
10667
10668        // All the formatting (including the command, which completed after the manual edit)
10669        // is undone together.
10670        editor.undo(&Default::default(), window, cx);
10671        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10672    });
10673}
10674
10675#[gpui::test]
10676async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10677    init_test(cx, |settings| {
10678        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10679            Formatter::LanguageServer { name: None },
10680        ])))
10681    });
10682
10683    let fs = FakeFs::new(cx.executor());
10684    fs.insert_file(path!("/file.ts"), Default::default()).await;
10685
10686    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10687
10688    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10689    language_registry.add(Arc::new(Language::new(
10690        LanguageConfig {
10691            name: "TypeScript".into(),
10692            matcher: LanguageMatcher {
10693                path_suffixes: vec!["ts".to_string()],
10694                ..Default::default()
10695            },
10696            ..LanguageConfig::default()
10697        },
10698        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10699    )));
10700    update_test_language_settings(cx, |settings| {
10701        settings.defaults.prettier = Some(PrettierSettings {
10702            allowed: true,
10703            ..PrettierSettings::default()
10704        });
10705    });
10706    let mut fake_servers = language_registry.register_fake_lsp(
10707        "TypeScript",
10708        FakeLspAdapter {
10709            capabilities: lsp::ServerCapabilities {
10710                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10711                ..Default::default()
10712            },
10713            ..Default::default()
10714        },
10715    );
10716
10717    let buffer = project
10718        .update(cx, |project, cx| {
10719            project.open_local_buffer(path!("/file.ts"), cx)
10720        })
10721        .await
10722        .unwrap();
10723
10724    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10725    let (editor, cx) = cx.add_window_view(|window, cx| {
10726        build_editor_with_project(project.clone(), buffer, window, cx)
10727    });
10728    editor.update_in(cx, |editor, window, cx| {
10729        editor.set_text(
10730            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10731            window,
10732            cx,
10733        )
10734    });
10735
10736    cx.executor().start_waiting();
10737    let fake_server = fake_servers.next().await.unwrap();
10738
10739    let format = editor
10740        .update_in(cx, |editor, window, cx| {
10741            editor.perform_code_action_kind(
10742                project.clone(),
10743                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10744                window,
10745                cx,
10746            )
10747        })
10748        .unwrap();
10749    fake_server
10750        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10751            assert_eq!(
10752                params.text_document.uri,
10753                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10754            );
10755            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10756                lsp::CodeAction {
10757                    title: "Organize Imports".to_string(),
10758                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10759                    edit: Some(lsp::WorkspaceEdit {
10760                        changes: Some(
10761                            [(
10762                                params.text_document.uri.clone(),
10763                                vec![lsp::TextEdit::new(
10764                                    lsp::Range::new(
10765                                        lsp::Position::new(1, 0),
10766                                        lsp::Position::new(2, 0),
10767                                    ),
10768                                    "".to_string(),
10769                                )],
10770                            )]
10771                            .into_iter()
10772                            .collect(),
10773                        ),
10774                        ..Default::default()
10775                    }),
10776                    ..Default::default()
10777                },
10778            )]))
10779        })
10780        .next()
10781        .await;
10782    cx.executor().start_waiting();
10783    format.await;
10784    assert_eq!(
10785        editor.update(cx, |editor, cx| editor.text(cx)),
10786        "import { a } from 'module';\n\nconst x = a;\n"
10787    );
10788
10789    editor.update_in(cx, |editor, window, cx| {
10790        editor.set_text(
10791            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10792            window,
10793            cx,
10794        )
10795    });
10796    // Ensure we don't lock if code action hangs.
10797    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10798        move |params, _| async move {
10799            assert_eq!(
10800                params.text_document.uri,
10801                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10802            );
10803            futures::future::pending::<()>().await;
10804            unreachable!()
10805        },
10806    );
10807    let format = editor
10808        .update_in(cx, |editor, window, cx| {
10809            editor.perform_code_action_kind(
10810                project,
10811                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10812                window,
10813                cx,
10814            )
10815        })
10816        .unwrap();
10817    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10818    cx.executor().start_waiting();
10819    format.await;
10820    assert_eq!(
10821        editor.update(cx, |editor, cx| editor.text(cx)),
10822        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10823    );
10824}
10825
10826#[gpui::test]
10827async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10828    init_test(cx, |_| {});
10829
10830    let mut cx = EditorLspTestContext::new_rust(
10831        lsp::ServerCapabilities {
10832            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10833            ..Default::default()
10834        },
10835        cx,
10836    )
10837    .await;
10838
10839    cx.set_state(indoc! {"
10840        one.twoˇ
10841    "});
10842
10843    // The format request takes a long time. When it completes, it inserts
10844    // a newline and an indent before the `.`
10845    cx.lsp
10846        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10847            let executor = cx.background_executor().clone();
10848            async move {
10849                executor.timer(Duration::from_millis(100)).await;
10850                Ok(Some(vec![lsp::TextEdit {
10851                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10852                    new_text: "\n    ".into(),
10853                }]))
10854            }
10855        });
10856
10857    // Submit a format request.
10858    let format_1 = cx
10859        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10860        .unwrap();
10861    cx.executor().run_until_parked();
10862
10863    // Submit a second format request.
10864    let format_2 = cx
10865        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10866        .unwrap();
10867    cx.executor().run_until_parked();
10868
10869    // Wait for both format requests to complete
10870    cx.executor().advance_clock(Duration::from_millis(200));
10871    cx.executor().start_waiting();
10872    format_1.await.unwrap();
10873    cx.executor().start_waiting();
10874    format_2.await.unwrap();
10875
10876    // The formatting edits only happens once.
10877    cx.assert_editor_state(indoc! {"
10878        one
10879            .twoˇ
10880    "});
10881}
10882
10883#[gpui::test]
10884async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10885    init_test(cx, |settings| {
10886        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10887    });
10888
10889    let mut cx = EditorLspTestContext::new_rust(
10890        lsp::ServerCapabilities {
10891            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10892            ..Default::default()
10893        },
10894        cx,
10895    )
10896    .await;
10897
10898    // Set up a buffer white some trailing whitespace and no trailing newline.
10899    cx.set_state(
10900        &[
10901            "one ",   //
10902            "twoˇ",   //
10903            "three ", //
10904            "four",   //
10905        ]
10906        .join("\n"),
10907    );
10908
10909    // Submit a format request.
10910    let format = cx
10911        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10912        .unwrap();
10913
10914    // Record which buffer changes have been sent to the language server
10915    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10916    cx.lsp
10917        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10918            let buffer_changes = buffer_changes.clone();
10919            move |params, _| {
10920                buffer_changes.lock().extend(
10921                    params
10922                        .content_changes
10923                        .into_iter()
10924                        .map(|e| (e.range.unwrap(), e.text)),
10925                );
10926            }
10927        });
10928
10929    // Handle formatting requests to the language server.
10930    cx.lsp
10931        .set_request_handler::<lsp::request::Formatting, _, _>({
10932            let buffer_changes = buffer_changes.clone();
10933            move |_, _| {
10934                // When formatting is requested, trailing whitespace has already been stripped,
10935                // and the trailing newline has already been added.
10936                assert_eq!(
10937                    &buffer_changes.lock()[1..],
10938                    &[
10939                        (
10940                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10941                            "".into()
10942                        ),
10943                        (
10944                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10945                            "".into()
10946                        ),
10947                        (
10948                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10949                            "\n".into()
10950                        ),
10951                    ]
10952                );
10953
10954                // Insert blank lines between each line of the buffer.
10955                async move {
10956                    Ok(Some(vec![
10957                        lsp::TextEdit {
10958                            range: lsp::Range::new(
10959                                lsp::Position::new(1, 0),
10960                                lsp::Position::new(1, 0),
10961                            ),
10962                            new_text: "\n".into(),
10963                        },
10964                        lsp::TextEdit {
10965                            range: lsp::Range::new(
10966                                lsp::Position::new(2, 0),
10967                                lsp::Position::new(2, 0),
10968                            ),
10969                            new_text: "\n".into(),
10970                        },
10971                    ]))
10972                }
10973            }
10974        });
10975
10976    // After formatting the buffer, the trailing whitespace is stripped,
10977    // a newline is appended, and the edits provided by the language server
10978    // have been applied.
10979    format.await.unwrap();
10980    cx.assert_editor_state(
10981        &[
10982            "one",   //
10983            "",      //
10984            "twoˇ",  //
10985            "",      //
10986            "three", //
10987            "four",  //
10988            "",      //
10989        ]
10990        .join("\n"),
10991    );
10992
10993    // Undoing the formatting undoes the trailing whitespace removal, the
10994    // trailing newline, and the LSP edits.
10995    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10996    cx.assert_editor_state(
10997        &[
10998            "one ",   //
10999            "twoˇ",   //
11000            "three ", //
11001            "four",   //
11002        ]
11003        .join("\n"),
11004    );
11005}
11006
11007#[gpui::test]
11008async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11009    cx: &mut TestAppContext,
11010) {
11011    init_test(cx, |_| {});
11012
11013    cx.update(|cx| {
11014        cx.update_global::<SettingsStore, _>(|settings, cx| {
11015            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11016                settings.auto_signature_help = Some(true);
11017            });
11018        });
11019    });
11020
11021    let mut cx = EditorLspTestContext::new_rust(
11022        lsp::ServerCapabilities {
11023            signature_help_provider: Some(lsp::SignatureHelpOptions {
11024                ..Default::default()
11025            }),
11026            ..Default::default()
11027        },
11028        cx,
11029    )
11030    .await;
11031
11032    let language = Language::new(
11033        LanguageConfig {
11034            name: "Rust".into(),
11035            brackets: BracketPairConfig {
11036                pairs: vec![
11037                    BracketPair {
11038                        start: "{".to_string(),
11039                        end: "}".to_string(),
11040                        close: true,
11041                        surround: true,
11042                        newline: true,
11043                    },
11044                    BracketPair {
11045                        start: "(".to_string(),
11046                        end: ")".to_string(),
11047                        close: true,
11048                        surround: true,
11049                        newline: true,
11050                    },
11051                    BracketPair {
11052                        start: "/*".to_string(),
11053                        end: " */".to_string(),
11054                        close: true,
11055                        surround: true,
11056                        newline: true,
11057                    },
11058                    BracketPair {
11059                        start: "[".to_string(),
11060                        end: "]".to_string(),
11061                        close: false,
11062                        surround: false,
11063                        newline: true,
11064                    },
11065                    BracketPair {
11066                        start: "\"".to_string(),
11067                        end: "\"".to_string(),
11068                        close: true,
11069                        surround: true,
11070                        newline: false,
11071                    },
11072                    BracketPair {
11073                        start: "<".to_string(),
11074                        end: ">".to_string(),
11075                        close: false,
11076                        surround: true,
11077                        newline: true,
11078                    },
11079                ],
11080                ..Default::default()
11081            },
11082            autoclose_before: "})]".to_string(),
11083            ..Default::default()
11084        },
11085        Some(tree_sitter_rust::LANGUAGE.into()),
11086    );
11087    let language = Arc::new(language);
11088
11089    cx.language_registry().add(language.clone());
11090    cx.update_buffer(|buffer, cx| {
11091        buffer.set_language(Some(language), cx);
11092    });
11093
11094    cx.set_state(
11095        &r#"
11096            fn main() {
11097                sampleˇ
11098            }
11099        "#
11100        .unindent(),
11101    );
11102
11103    cx.update_editor(|editor, window, cx| {
11104        editor.handle_input("(", window, cx);
11105    });
11106    cx.assert_editor_state(
11107        &"
11108            fn main() {
11109                sample(ˇ)
11110            }
11111        "
11112        .unindent(),
11113    );
11114
11115    let mocked_response = lsp::SignatureHelp {
11116        signatures: vec![lsp::SignatureInformation {
11117            label: "fn sample(param1: u8, param2: u8)".to_string(),
11118            documentation: None,
11119            parameters: Some(vec![
11120                lsp::ParameterInformation {
11121                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11122                    documentation: None,
11123                },
11124                lsp::ParameterInformation {
11125                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11126                    documentation: None,
11127                },
11128            ]),
11129            active_parameter: None,
11130        }],
11131        active_signature: Some(0),
11132        active_parameter: Some(0),
11133    };
11134    handle_signature_help_request(&mut cx, mocked_response).await;
11135
11136    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11137        .await;
11138
11139    cx.editor(|editor, _, _| {
11140        let signature_help_state = editor.signature_help_state.popover().cloned();
11141        let signature = signature_help_state.unwrap();
11142        assert_eq!(
11143            signature.signatures[signature.current_signature].label,
11144            "fn sample(param1: u8, param2: u8)"
11145        );
11146    });
11147}
11148
11149#[gpui::test]
11150async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11151    init_test(cx, |_| {});
11152
11153    cx.update(|cx| {
11154        cx.update_global::<SettingsStore, _>(|settings, cx| {
11155            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11156                settings.auto_signature_help = Some(false);
11157                settings.show_signature_help_after_edits = Some(false);
11158            });
11159        });
11160    });
11161
11162    let mut cx = EditorLspTestContext::new_rust(
11163        lsp::ServerCapabilities {
11164            signature_help_provider: Some(lsp::SignatureHelpOptions {
11165                ..Default::default()
11166            }),
11167            ..Default::default()
11168        },
11169        cx,
11170    )
11171    .await;
11172
11173    let language = Language::new(
11174        LanguageConfig {
11175            name: "Rust".into(),
11176            brackets: BracketPairConfig {
11177                pairs: vec![
11178                    BracketPair {
11179                        start: "{".to_string(),
11180                        end: "}".to_string(),
11181                        close: true,
11182                        surround: true,
11183                        newline: true,
11184                    },
11185                    BracketPair {
11186                        start: "(".to_string(),
11187                        end: ")".to_string(),
11188                        close: true,
11189                        surround: true,
11190                        newline: true,
11191                    },
11192                    BracketPair {
11193                        start: "/*".to_string(),
11194                        end: " */".to_string(),
11195                        close: true,
11196                        surround: true,
11197                        newline: true,
11198                    },
11199                    BracketPair {
11200                        start: "[".to_string(),
11201                        end: "]".to_string(),
11202                        close: false,
11203                        surround: false,
11204                        newline: true,
11205                    },
11206                    BracketPair {
11207                        start: "\"".to_string(),
11208                        end: "\"".to_string(),
11209                        close: true,
11210                        surround: true,
11211                        newline: false,
11212                    },
11213                    BracketPair {
11214                        start: "<".to_string(),
11215                        end: ">".to_string(),
11216                        close: false,
11217                        surround: true,
11218                        newline: true,
11219                    },
11220                ],
11221                ..Default::default()
11222            },
11223            autoclose_before: "})]".to_string(),
11224            ..Default::default()
11225        },
11226        Some(tree_sitter_rust::LANGUAGE.into()),
11227    );
11228    let language = Arc::new(language);
11229
11230    cx.language_registry().add(language.clone());
11231    cx.update_buffer(|buffer, cx| {
11232        buffer.set_language(Some(language), cx);
11233    });
11234
11235    // Ensure that signature_help is not called when no signature help is enabled.
11236    cx.set_state(
11237        &r#"
11238            fn main() {
11239                sampleˇ
11240            }
11241        "#
11242        .unindent(),
11243    );
11244    cx.update_editor(|editor, window, cx| {
11245        editor.handle_input("(", window, cx);
11246    });
11247    cx.assert_editor_state(
11248        &"
11249            fn main() {
11250                sample(ˇ)
11251            }
11252        "
11253        .unindent(),
11254    );
11255    cx.editor(|editor, _, _| {
11256        assert!(editor.signature_help_state.task().is_none());
11257    });
11258
11259    let mocked_response = lsp::SignatureHelp {
11260        signatures: vec![lsp::SignatureInformation {
11261            label: "fn sample(param1: u8, param2: u8)".to_string(),
11262            documentation: None,
11263            parameters: Some(vec![
11264                lsp::ParameterInformation {
11265                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11266                    documentation: None,
11267                },
11268                lsp::ParameterInformation {
11269                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11270                    documentation: None,
11271                },
11272            ]),
11273            active_parameter: None,
11274        }],
11275        active_signature: Some(0),
11276        active_parameter: Some(0),
11277    };
11278
11279    // Ensure that signature_help is called when enabled afte edits
11280    cx.update(|_, cx| {
11281        cx.update_global::<SettingsStore, _>(|settings, cx| {
11282            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11283                settings.auto_signature_help = Some(false);
11284                settings.show_signature_help_after_edits = Some(true);
11285            });
11286        });
11287    });
11288    cx.set_state(
11289        &r#"
11290            fn main() {
11291                sampleˇ
11292            }
11293        "#
11294        .unindent(),
11295    );
11296    cx.update_editor(|editor, window, cx| {
11297        editor.handle_input("(", window, cx);
11298    });
11299    cx.assert_editor_state(
11300        &"
11301            fn main() {
11302                sample(ˇ)
11303            }
11304        "
11305        .unindent(),
11306    );
11307    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11308    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11309        .await;
11310    cx.update_editor(|editor, _, _| {
11311        let signature_help_state = editor.signature_help_state.popover().cloned();
11312        assert!(signature_help_state.is_some());
11313        let signature = signature_help_state.unwrap();
11314        assert_eq!(
11315            signature.signatures[signature.current_signature].label,
11316            "fn sample(param1: u8, param2: u8)"
11317        );
11318        editor.signature_help_state = SignatureHelpState::default();
11319    });
11320
11321    // Ensure that signature_help is called when auto signature help override is enabled
11322    cx.update(|_, cx| {
11323        cx.update_global::<SettingsStore, _>(|settings, cx| {
11324            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11325                settings.auto_signature_help = Some(true);
11326                settings.show_signature_help_after_edits = Some(false);
11327            });
11328        });
11329    });
11330    cx.set_state(
11331        &r#"
11332            fn main() {
11333                sampleˇ
11334            }
11335        "#
11336        .unindent(),
11337    );
11338    cx.update_editor(|editor, window, cx| {
11339        editor.handle_input("(", window, cx);
11340    });
11341    cx.assert_editor_state(
11342        &"
11343            fn main() {
11344                sample(ˇ)
11345            }
11346        "
11347        .unindent(),
11348    );
11349    handle_signature_help_request(&mut cx, mocked_response).await;
11350    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11351        .await;
11352    cx.editor(|editor, _, _| {
11353        let signature_help_state = editor.signature_help_state.popover().cloned();
11354        assert!(signature_help_state.is_some());
11355        let signature = signature_help_state.unwrap();
11356        assert_eq!(
11357            signature.signatures[signature.current_signature].label,
11358            "fn sample(param1: u8, param2: u8)"
11359        );
11360    });
11361}
11362
11363#[gpui::test]
11364async fn test_signature_help(cx: &mut TestAppContext) {
11365    init_test(cx, |_| {});
11366    cx.update(|cx| {
11367        cx.update_global::<SettingsStore, _>(|settings, cx| {
11368            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11369                settings.auto_signature_help = Some(true);
11370            });
11371        });
11372    });
11373
11374    let mut cx = EditorLspTestContext::new_rust(
11375        lsp::ServerCapabilities {
11376            signature_help_provider: Some(lsp::SignatureHelpOptions {
11377                ..Default::default()
11378            }),
11379            ..Default::default()
11380        },
11381        cx,
11382    )
11383    .await;
11384
11385    // A test that directly calls `show_signature_help`
11386    cx.update_editor(|editor, window, cx| {
11387        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11388    });
11389
11390    let mocked_response = lsp::SignatureHelp {
11391        signatures: vec![lsp::SignatureInformation {
11392            label: "fn sample(param1: u8, param2: u8)".to_string(),
11393            documentation: None,
11394            parameters: Some(vec![
11395                lsp::ParameterInformation {
11396                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11397                    documentation: None,
11398                },
11399                lsp::ParameterInformation {
11400                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11401                    documentation: None,
11402                },
11403            ]),
11404            active_parameter: None,
11405        }],
11406        active_signature: Some(0),
11407        active_parameter: Some(0),
11408    };
11409    handle_signature_help_request(&mut cx, mocked_response).await;
11410
11411    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11412        .await;
11413
11414    cx.editor(|editor, _, _| {
11415        let signature_help_state = editor.signature_help_state.popover().cloned();
11416        assert!(signature_help_state.is_some());
11417        let signature = signature_help_state.unwrap();
11418        assert_eq!(
11419            signature.signatures[signature.current_signature].label,
11420            "fn sample(param1: u8, param2: u8)"
11421        );
11422    });
11423
11424    // When exiting outside from inside the brackets, `signature_help` is closed.
11425    cx.set_state(indoc! {"
11426        fn main() {
11427            sample(ˇ);
11428        }
11429
11430        fn sample(param1: u8, param2: u8) {}
11431    "});
11432
11433    cx.update_editor(|editor, window, cx| {
11434        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11435            s.select_ranges([0..0])
11436        });
11437    });
11438
11439    let mocked_response = lsp::SignatureHelp {
11440        signatures: Vec::new(),
11441        active_signature: None,
11442        active_parameter: None,
11443    };
11444    handle_signature_help_request(&mut cx, mocked_response).await;
11445
11446    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11447        .await;
11448
11449    cx.editor(|editor, _, _| {
11450        assert!(!editor.signature_help_state.is_shown());
11451    });
11452
11453    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11454    cx.set_state(indoc! {"
11455        fn main() {
11456            sample(ˇ);
11457        }
11458
11459        fn sample(param1: u8, param2: u8) {}
11460    "});
11461
11462    let mocked_response = lsp::SignatureHelp {
11463        signatures: vec![lsp::SignatureInformation {
11464            label: "fn sample(param1: u8, param2: u8)".to_string(),
11465            documentation: None,
11466            parameters: Some(vec![
11467                lsp::ParameterInformation {
11468                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11469                    documentation: None,
11470                },
11471                lsp::ParameterInformation {
11472                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11473                    documentation: None,
11474                },
11475            ]),
11476            active_parameter: None,
11477        }],
11478        active_signature: Some(0),
11479        active_parameter: Some(0),
11480    };
11481    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11482    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11483        .await;
11484    cx.editor(|editor, _, _| {
11485        assert!(editor.signature_help_state.is_shown());
11486    });
11487
11488    // Restore the popover with more parameter input
11489    cx.set_state(indoc! {"
11490        fn main() {
11491            sample(param1, param2ˇ);
11492        }
11493
11494        fn sample(param1: u8, param2: u8) {}
11495    "});
11496
11497    let mocked_response = lsp::SignatureHelp {
11498        signatures: vec![lsp::SignatureInformation {
11499            label: "fn sample(param1: u8, param2: u8)".to_string(),
11500            documentation: None,
11501            parameters: Some(vec![
11502                lsp::ParameterInformation {
11503                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11504                    documentation: None,
11505                },
11506                lsp::ParameterInformation {
11507                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11508                    documentation: None,
11509                },
11510            ]),
11511            active_parameter: None,
11512        }],
11513        active_signature: Some(0),
11514        active_parameter: Some(1),
11515    };
11516    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11517    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11518        .await;
11519
11520    // When selecting a range, the popover is gone.
11521    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11522    cx.update_editor(|editor, window, cx| {
11523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11524            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11525        })
11526    });
11527    cx.assert_editor_state(indoc! {"
11528        fn main() {
11529            sample(param1, «ˇparam2»);
11530        }
11531
11532        fn sample(param1: u8, param2: u8) {}
11533    "});
11534    cx.editor(|editor, _, _| {
11535        assert!(!editor.signature_help_state.is_shown());
11536    });
11537
11538    // When unselecting again, the popover is back if within the brackets.
11539    cx.update_editor(|editor, window, cx| {
11540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11541            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11542        })
11543    });
11544    cx.assert_editor_state(indoc! {"
11545        fn main() {
11546            sample(param1, ˇparam2);
11547        }
11548
11549        fn sample(param1: u8, param2: u8) {}
11550    "});
11551    handle_signature_help_request(&mut cx, mocked_response).await;
11552    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11553        .await;
11554    cx.editor(|editor, _, _| {
11555        assert!(editor.signature_help_state.is_shown());
11556    });
11557
11558    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11559    cx.update_editor(|editor, window, cx| {
11560        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11561            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11562            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11563        })
11564    });
11565    cx.assert_editor_state(indoc! {"
11566        fn main() {
11567            sample(param1, ˇparam2);
11568        }
11569
11570        fn sample(param1: u8, param2: u8) {}
11571    "});
11572
11573    let mocked_response = lsp::SignatureHelp {
11574        signatures: vec![lsp::SignatureInformation {
11575            label: "fn sample(param1: u8, param2: u8)".to_string(),
11576            documentation: None,
11577            parameters: Some(vec![
11578                lsp::ParameterInformation {
11579                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11580                    documentation: None,
11581                },
11582                lsp::ParameterInformation {
11583                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11584                    documentation: None,
11585                },
11586            ]),
11587            active_parameter: None,
11588        }],
11589        active_signature: Some(0),
11590        active_parameter: Some(1),
11591    };
11592    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11593    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11594        .await;
11595    cx.update_editor(|editor, _, cx| {
11596        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11597    });
11598    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11599        .await;
11600    cx.update_editor(|editor, window, cx| {
11601        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11602            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11603        })
11604    });
11605    cx.assert_editor_state(indoc! {"
11606        fn main() {
11607            sample(param1, «ˇparam2»);
11608        }
11609
11610        fn sample(param1: u8, param2: u8) {}
11611    "});
11612    cx.update_editor(|editor, window, cx| {
11613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11614            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11615        })
11616    });
11617    cx.assert_editor_state(indoc! {"
11618        fn main() {
11619            sample(param1, ˇparam2);
11620        }
11621
11622        fn sample(param1: u8, param2: u8) {}
11623    "});
11624    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11625        .await;
11626}
11627
11628#[gpui::test]
11629async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11630    init_test(cx, |_| {});
11631
11632    let mut cx = EditorLspTestContext::new_rust(
11633        lsp::ServerCapabilities {
11634            signature_help_provider: Some(lsp::SignatureHelpOptions {
11635                ..Default::default()
11636            }),
11637            ..Default::default()
11638        },
11639        cx,
11640    )
11641    .await;
11642
11643    cx.set_state(indoc! {"
11644        fn main() {
11645            overloadedˇ
11646        }
11647    "});
11648
11649    cx.update_editor(|editor, window, cx| {
11650        editor.handle_input("(", window, cx);
11651        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11652    });
11653
11654    // Mock response with 3 signatures
11655    let mocked_response = lsp::SignatureHelp {
11656        signatures: vec![
11657            lsp::SignatureInformation {
11658                label: "fn overloaded(x: i32)".to_string(),
11659                documentation: None,
11660                parameters: Some(vec![lsp::ParameterInformation {
11661                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11662                    documentation: None,
11663                }]),
11664                active_parameter: None,
11665            },
11666            lsp::SignatureInformation {
11667                label: "fn overloaded(x: i32, y: i32)".to_string(),
11668                documentation: None,
11669                parameters: Some(vec![
11670                    lsp::ParameterInformation {
11671                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11672                        documentation: None,
11673                    },
11674                    lsp::ParameterInformation {
11675                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11676                        documentation: None,
11677                    },
11678                ]),
11679                active_parameter: None,
11680            },
11681            lsp::SignatureInformation {
11682                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11683                documentation: None,
11684                parameters: Some(vec![
11685                    lsp::ParameterInformation {
11686                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11687                        documentation: None,
11688                    },
11689                    lsp::ParameterInformation {
11690                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11691                        documentation: None,
11692                    },
11693                    lsp::ParameterInformation {
11694                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11695                        documentation: None,
11696                    },
11697                ]),
11698                active_parameter: None,
11699            },
11700        ],
11701        active_signature: Some(1),
11702        active_parameter: Some(0),
11703    };
11704    handle_signature_help_request(&mut cx, mocked_response).await;
11705
11706    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11707        .await;
11708
11709    // Verify we have multiple signatures and the right one is selected
11710    cx.editor(|editor, _, _| {
11711        let popover = editor.signature_help_state.popover().cloned().unwrap();
11712        assert_eq!(popover.signatures.len(), 3);
11713        // active_signature was 1, so that should be the current
11714        assert_eq!(popover.current_signature, 1);
11715        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11716        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11717        assert_eq!(
11718            popover.signatures[2].label,
11719            "fn overloaded(x: i32, y: i32, z: i32)"
11720        );
11721    });
11722
11723    // Test navigation functionality
11724    cx.update_editor(|editor, window, cx| {
11725        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11726    });
11727
11728    cx.editor(|editor, _, _| {
11729        let popover = editor.signature_help_state.popover().cloned().unwrap();
11730        assert_eq!(popover.current_signature, 2);
11731    });
11732
11733    // Test wrap around
11734    cx.update_editor(|editor, window, cx| {
11735        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11736    });
11737
11738    cx.editor(|editor, _, _| {
11739        let popover = editor.signature_help_state.popover().cloned().unwrap();
11740        assert_eq!(popover.current_signature, 0);
11741    });
11742
11743    // Test previous navigation
11744    cx.update_editor(|editor, window, cx| {
11745        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11746    });
11747
11748    cx.editor(|editor, _, _| {
11749        let popover = editor.signature_help_state.popover().cloned().unwrap();
11750        assert_eq!(popover.current_signature, 2);
11751    });
11752}
11753
11754#[gpui::test]
11755async fn test_completion_mode(cx: &mut TestAppContext) {
11756    init_test(cx, |_| {});
11757    let mut cx = EditorLspTestContext::new_rust(
11758        lsp::ServerCapabilities {
11759            completion_provider: Some(lsp::CompletionOptions {
11760                resolve_provider: Some(true),
11761                ..Default::default()
11762            }),
11763            ..Default::default()
11764        },
11765        cx,
11766    )
11767    .await;
11768
11769    struct Run {
11770        run_description: &'static str,
11771        initial_state: String,
11772        buffer_marked_text: String,
11773        completion_label: &'static str,
11774        completion_text: &'static str,
11775        expected_with_insert_mode: String,
11776        expected_with_replace_mode: String,
11777        expected_with_replace_subsequence_mode: String,
11778        expected_with_replace_suffix_mode: String,
11779    }
11780
11781    let runs = [
11782        Run {
11783            run_description: "Start of word matches completion text",
11784            initial_state: "before ediˇ after".into(),
11785            buffer_marked_text: "before <edi|> after".into(),
11786            completion_label: "editor",
11787            completion_text: "editor",
11788            expected_with_insert_mode: "before editorˇ after".into(),
11789            expected_with_replace_mode: "before editorˇ after".into(),
11790            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11791            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11792        },
11793        Run {
11794            run_description: "Accept same text at the middle of the word",
11795            initial_state: "before ediˇtor after".into(),
11796            buffer_marked_text: "before <edi|tor> after".into(),
11797            completion_label: "editor",
11798            completion_text: "editor",
11799            expected_with_insert_mode: "before editorˇtor after".into(),
11800            expected_with_replace_mode: "before editorˇ after".into(),
11801            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11802            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11803        },
11804        Run {
11805            run_description: "End of word matches completion text -- cursor at end",
11806            initial_state: "before torˇ after".into(),
11807            buffer_marked_text: "before <tor|> after".into(),
11808            completion_label: "editor",
11809            completion_text: "editor",
11810            expected_with_insert_mode: "before editorˇ after".into(),
11811            expected_with_replace_mode: "before editorˇ after".into(),
11812            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11813            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11814        },
11815        Run {
11816            run_description: "End of word matches completion text -- cursor at start",
11817            initial_state: "before ˇtor after".into(),
11818            buffer_marked_text: "before <|tor> after".into(),
11819            completion_label: "editor",
11820            completion_text: "editor",
11821            expected_with_insert_mode: "before editorˇtor after".into(),
11822            expected_with_replace_mode: "before editorˇ after".into(),
11823            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11824            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11825        },
11826        Run {
11827            run_description: "Prepend text containing whitespace",
11828            initial_state: "pˇfield: bool".into(),
11829            buffer_marked_text: "<p|field>: bool".into(),
11830            completion_label: "pub ",
11831            completion_text: "pub ",
11832            expected_with_insert_mode: "pub ˇfield: bool".into(),
11833            expected_with_replace_mode: "pub ˇ: bool".into(),
11834            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11835            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11836        },
11837        Run {
11838            run_description: "Add element to start of list",
11839            initial_state: "[element_ˇelement_2]".into(),
11840            buffer_marked_text: "[<element_|element_2>]".into(),
11841            completion_label: "element_1",
11842            completion_text: "element_1",
11843            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11844            expected_with_replace_mode: "[element_1ˇ]".into(),
11845            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11846            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11847        },
11848        Run {
11849            run_description: "Add element to start of list -- first and second elements are equal",
11850            initial_state: "[elˇelement]".into(),
11851            buffer_marked_text: "[<el|element>]".into(),
11852            completion_label: "element",
11853            completion_text: "element",
11854            expected_with_insert_mode: "[elementˇelement]".into(),
11855            expected_with_replace_mode: "[elementˇ]".into(),
11856            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11857            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11858        },
11859        Run {
11860            run_description: "Ends with matching suffix",
11861            initial_state: "SubˇError".into(),
11862            buffer_marked_text: "<Sub|Error>".into(),
11863            completion_label: "SubscriptionError",
11864            completion_text: "SubscriptionError",
11865            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11866            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11867            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11868            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11869        },
11870        Run {
11871            run_description: "Suffix is a subsequence -- contiguous",
11872            initial_state: "SubˇErr".into(),
11873            buffer_marked_text: "<Sub|Err>".into(),
11874            completion_label: "SubscriptionError",
11875            completion_text: "SubscriptionError",
11876            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11877            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11878            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11879            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11880        },
11881        Run {
11882            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11883            initial_state: "Suˇscrirr".into(),
11884            buffer_marked_text: "<Su|scrirr>".into(),
11885            completion_label: "SubscriptionError",
11886            completion_text: "SubscriptionError",
11887            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11888            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11889            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11890            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11891        },
11892        Run {
11893            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11894            initial_state: "foo(indˇix)".into(),
11895            buffer_marked_text: "foo(<ind|ix>)".into(),
11896            completion_label: "node_index",
11897            completion_text: "node_index",
11898            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11899            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11900            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11901            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11902        },
11903        Run {
11904            run_description: "Replace range ends before cursor - should extend to cursor",
11905            initial_state: "before editˇo after".into(),
11906            buffer_marked_text: "before <{ed}>it|o after".into(),
11907            completion_label: "editor",
11908            completion_text: "editor",
11909            expected_with_insert_mode: "before editorˇo after".into(),
11910            expected_with_replace_mode: "before editorˇo after".into(),
11911            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11912            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11913        },
11914        Run {
11915            run_description: "Uses label for suffix matching",
11916            initial_state: "before ediˇtor after".into(),
11917            buffer_marked_text: "before <edi|tor> after".into(),
11918            completion_label: "editor",
11919            completion_text: "editor()",
11920            expected_with_insert_mode: "before editor()ˇtor after".into(),
11921            expected_with_replace_mode: "before editor()ˇ after".into(),
11922            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11923            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11924        },
11925        Run {
11926            run_description: "Case insensitive subsequence and suffix matching",
11927            initial_state: "before EDiˇtoR after".into(),
11928            buffer_marked_text: "before <EDi|toR> after".into(),
11929            completion_label: "editor",
11930            completion_text: "editor",
11931            expected_with_insert_mode: "before editorˇtoR after".into(),
11932            expected_with_replace_mode: "before editorˇ after".into(),
11933            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11934            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11935        },
11936    ];
11937
11938    for run in runs {
11939        let run_variations = [
11940            (LspInsertMode::Insert, run.expected_with_insert_mode),
11941            (LspInsertMode::Replace, run.expected_with_replace_mode),
11942            (
11943                LspInsertMode::ReplaceSubsequence,
11944                run.expected_with_replace_subsequence_mode,
11945            ),
11946            (
11947                LspInsertMode::ReplaceSuffix,
11948                run.expected_with_replace_suffix_mode,
11949            ),
11950        ];
11951
11952        for (lsp_insert_mode, expected_text) in run_variations {
11953            eprintln!(
11954                "run = {:?}, mode = {lsp_insert_mode:.?}",
11955                run.run_description,
11956            );
11957
11958            update_test_language_settings(&mut cx, |settings| {
11959                settings.defaults.completions = Some(CompletionSettings {
11960                    lsp_insert_mode,
11961                    words: WordsCompletionMode::Disabled,
11962                    lsp: true,
11963                    lsp_fetch_timeout_ms: 0,
11964                });
11965            });
11966
11967            cx.set_state(&run.initial_state);
11968            cx.update_editor(|editor, window, cx| {
11969                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11970            });
11971
11972            let counter = Arc::new(AtomicUsize::new(0));
11973            handle_completion_request_with_insert_and_replace(
11974                &mut cx,
11975                &run.buffer_marked_text,
11976                vec![(run.completion_label, run.completion_text)],
11977                counter.clone(),
11978            )
11979            .await;
11980            cx.condition(|editor, _| editor.context_menu_visible())
11981                .await;
11982            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11983
11984            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11985                editor
11986                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11987                    .unwrap()
11988            });
11989            cx.assert_editor_state(&expected_text);
11990            handle_resolve_completion_request(&mut cx, None).await;
11991            apply_additional_edits.await.unwrap();
11992        }
11993    }
11994}
11995
11996#[gpui::test]
11997async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11998    init_test(cx, |_| {});
11999    let mut cx = EditorLspTestContext::new_rust(
12000        lsp::ServerCapabilities {
12001            completion_provider: Some(lsp::CompletionOptions {
12002                resolve_provider: Some(true),
12003                ..Default::default()
12004            }),
12005            ..Default::default()
12006        },
12007        cx,
12008    )
12009    .await;
12010
12011    let initial_state = "SubˇError";
12012    let buffer_marked_text = "<Sub|Error>";
12013    let completion_text = "SubscriptionError";
12014    let expected_with_insert_mode = "SubscriptionErrorˇError";
12015    let expected_with_replace_mode = "SubscriptionErrorˇ";
12016
12017    update_test_language_settings(&mut cx, |settings| {
12018        settings.defaults.completions = Some(CompletionSettings {
12019            words: WordsCompletionMode::Disabled,
12020            // set the opposite here to ensure that the action is overriding the default behavior
12021            lsp_insert_mode: LspInsertMode::Insert,
12022            lsp: true,
12023            lsp_fetch_timeout_ms: 0,
12024        });
12025    });
12026
12027    cx.set_state(initial_state);
12028    cx.update_editor(|editor, window, cx| {
12029        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12030    });
12031
12032    let counter = Arc::new(AtomicUsize::new(0));
12033    handle_completion_request_with_insert_and_replace(
12034        &mut cx,
12035        &buffer_marked_text,
12036        vec![(completion_text, completion_text)],
12037        counter.clone(),
12038    )
12039    .await;
12040    cx.condition(|editor, _| editor.context_menu_visible())
12041        .await;
12042    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12043
12044    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12045        editor
12046            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12047            .unwrap()
12048    });
12049    cx.assert_editor_state(&expected_with_replace_mode);
12050    handle_resolve_completion_request(&mut cx, None).await;
12051    apply_additional_edits.await.unwrap();
12052
12053    update_test_language_settings(&mut cx, |settings| {
12054        settings.defaults.completions = Some(CompletionSettings {
12055            words: WordsCompletionMode::Disabled,
12056            // set the opposite here to ensure that the action is overriding the default behavior
12057            lsp_insert_mode: LspInsertMode::Replace,
12058            lsp: true,
12059            lsp_fetch_timeout_ms: 0,
12060        });
12061    });
12062
12063    cx.set_state(initial_state);
12064    cx.update_editor(|editor, window, cx| {
12065        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12066    });
12067    handle_completion_request_with_insert_and_replace(
12068        &mut cx,
12069        &buffer_marked_text,
12070        vec![(completion_text, completion_text)],
12071        counter.clone(),
12072    )
12073    .await;
12074    cx.condition(|editor, _| editor.context_menu_visible())
12075        .await;
12076    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12077
12078    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12079        editor
12080            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12081            .unwrap()
12082    });
12083    cx.assert_editor_state(&expected_with_insert_mode);
12084    handle_resolve_completion_request(&mut cx, None).await;
12085    apply_additional_edits.await.unwrap();
12086}
12087
12088#[gpui::test]
12089async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12090    init_test(cx, |_| {});
12091    let mut cx = EditorLspTestContext::new_rust(
12092        lsp::ServerCapabilities {
12093            completion_provider: Some(lsp::CompletionOptions {
12094                resolve_provider: Some(true),
12095                ..Default::default()
12096            }),
12097            ..Default::default()
12098        },
12099        cx,
12100    )
12101    .await;
12102
12103    // scenario: surrounding text matches completion text
12104    let completion_text = "to_offset";
12105    let initial_state = indoc! {"
12106        1. buf.to_offˇsuffix
12107        2. buf.to_offˇsuf
12108        3. buf.to_offˇfix
12109        4. buf.to_offˇ
12110        5. into_offˇensive
12111        6. ˇsuffix
12112        7. let ˇ //
12113        8. aaˇzz
12114        9. buf.to_off«zzzzzˇ»suffix
12115        10. buf.«ˇzzzzz»suffix
12116        11. to_off«ˇzzzzz»
12117
12118        buf.to_offˇsuffix  // newest cursor
12119    "};
12120    let completion_marked_buffer = indoc! {"
12121        1. buf.to_offsuffix
12122        2. buf.to_offsuf
12123        3. buf.to_offfix
12124        4. buf.to_off
12125        5. into_offensive
12126        6. suffix
12127        7. let  //
12128        8. aazz
12129        9. buf.to_offzzzzzsuffix
12130        10. buf.zzzzzsuffix
12131        11. to_offzzzzz
12132
12133        buf.<to_off|suffix>  // newest cursor
12134    "};
12135    let expected = indoc! {"
12136        1. buf.to_offsetˇ
12137        2. buf.to_offsetˇsuf
12138        3. buf.to_offsetˇfix
12139        4. buf.to_offsetˇ
12140        5. into_offsetˇensive
12141        6. to_offsetˇsuffix
12142        7. let to_offsetˇ //
12143        8. aato_offsetˇzz
12144        9. buf.to_offsetˇ
12145        10. buf.to_offsetˇsuffix
12146        11. to_offsetˇ
12147
12148        buf.to_offsetˇ  // newest cursor
12149    "};
12150    cx.set_state(initial_state);
12151    cx.update_editor(|editor, window, cx| {
12152        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12153    });
12154    handle_completion_request_with_insert_and_replace(
12155        &mut cx,
12156        completion_marked_buffer,
12157        vec![(completion_text, completion_text)],
12158        Arc::new(AtomicUsize::new(0)),
12159    )
12160    .await;
12161    cx.condition(|editor, _| editor.context_menu_visible())
12162        .await;
12163    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12164        editor
12165            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12166            .unwrap()
12167    });
12168    cx.assert_editor_state(expected);
12169    handle_resolve_completion_request(&mut cx, None).await;
12170    apply_additional_edits.await.unwrap();
12171
12172    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12173    let completion_text = "foo_and_bar";
12174    let initial_state = indoc! {"
12175        1. ooanbˇ
12176        2. zooanbˇ
12177        3. ooanbˇz
12178        4. zooanbˇz
12179        5. ooanˇ
12180        6. oanbˇ
12181
12182        ooanbˇ
12183    "};
12184    let completion_marked_buffer = indoc! {"
12185        1. ooanb
12186        2. zooanb
12187        3. ooanbz
12188        4. zooanbz
12189        5. ooan
12190        6. oanb
12191
12192        <ooanb|>
12193    "};
12194    let expected = indoc! {"
12195        1. foo_and_barˇ
12196        2. zfoo_and_barˇ
12197        3. foo_and_barˇz
12198        4. zfoo_and_barˇz
12199        5. ooanfoo_and_barˇ
12200        6. oanbfoo_and_barˇ
12201
12202        foo_and_barˇ
12203    "};
12204    cx.set_state(initial_state);
12205    cx.update_editor(|editor, window, cx| {
12206        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12207    });
12208    handle_completion_request_with_insert_and_replace(
12209        &mut cx,
12210        completion_marked_buffer,
12211        vec![(completion_text, completion_text)],
12212        Arc::new(AtomicUsize::new(0)),
12213    )
12214    .await;
12215    cx.condition(|editor, _| editor.context_menu_visible())
12216        .await;
12217    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12218        editor
12219            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12220            .unwrap()
12221    });
12222    cx.assert_editor_state(expected);
12223    handle_resolve_completion_request(&mut cx, None).await;
12224    apply_additional_edits.await.unwrap();
12225
12226    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12227    // (expects the same as if it was inserted at the end)
12228    let completion_text = "foo_and_bar";
12229    let initial_state = indoc! {"
12230        1. ooˇanb
12231        2. zooˇanb
12232        3. ooˇanbz
12233        4. zooˇanbz
12234
12235        ooˇanb
12236    "};
12237    let completion_marked_buffer = indoc! {"
12238        1. ooanb
12239        2. zooanb
12240        3. ooanbz
12241        4. zooanbz
12242
12243        <oo|anb>
12244    "};
12245    let expected = indoc! {"
12246        1. foo_and_barˇ
12247        2. zfoo_and_barˇ
12248        3. foo_and_barˇz
12249        4. zfoo_and_barˇz
12250
12251        foo_and_barˇ
12252    "};
12253    cx.set_state(initial_state);
12254    cx.update_editor(|editor, window, cx| {
12255        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12256    });
12257    handle_completion_request_with_insert_and_replace(
12258        &mut cx,
12259        completion_marked_buffer,
12260        vec![(completion_text, completion_text)],
12261        Arc::new(AtomicUsize::new(0)),
12262    )
12263    .await;
12264    cx.condition(|editor, _| editor.context_menu_visible())
12265        .await;
12266    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12267        editor
12268            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12269            .unwrap()
12270    });
12271    cx.assert_editor_state(expected);
12272    handle_resolve_completion_request(&mut cx, None).await;
12273    apply_additional_edits.await.unwrap();
12274}
12275
12276// This used to crash
12277#[gpui::test]
12278async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12279    init_test(cx, |_| {});
12280
12281    let buffer_text = indoc! {"
12282        fn main() {
12283            10.satu;
12284
12285            //
12286            // separate cursors so they open in different excerpts (manually reproducible)
12287            //
12288
12289            10.satu20;
12290        }
12291    "};
12292    let multibuffer_text_with_selections = indoc! {"
12293        fn main() {
12294            10.satuˇ;
12295
12296            //
12297
12298            //
12299
12300            10.satuˇ20;
12301        }
12302    "};
12303    let expected_multibuffer = indoc! {"
12304        fn main() {
12305            10.saturating_sub()ˇ;
12306
12307            //
12308
12309            //
12310
12311            10.saturating_sub()ˇ;
12312        }
12313    "};
12314
12315    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12316    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12317
12318    let fs = FakeFs::new(cx.executor());
12319    fs.insert_tree(
12320        path!("/a"),
12321        json!({
12322            "main.rs": buffer_text,
12323        }),
12324    )
12325    .await;
12326
12327    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12328    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12329    language_registry.add(rust_lang());
12330    let mut fake_servers = language_registry.register_fake_lsp(
12331        "Rust",
12332        FakeLspAdapter {
12333            capabilities: lsp::ServerCapabilities {
12334                completion_provider: Some(lsp::CompletionOptions {
12335                    resolve_provider: None,
12336                    ..lsp::CompletionOptions::default()
12337                }),
12338                ..lsp::ServerCapabilities::default()
12339            },
12340            ..FakeLspAdapter::default()
12341        },
12342    );
12343    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12344    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12345    let buffer = project
12346        .update(cx, |project, cx| {
12347            project.open_local_buffer(path!("/a/main.rs"), cx)
12348        })
12349        .await
12350        .unwrap();
12351
12352    let multi_buffer = cx.new(|cx| {
12353        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12354        multi_buffer.push_excerpts(
12355            buffer.clone(),
12356            [ExcerptRange::new(0..first_excerpt_end)],
12357            cx,
12358        );
12359        multi_buffer.push_excerpts(
12360            buffer.clone(),
12361            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12362            cx,
12363        );
12364        multi_buffer
12365    });
12366
12367    let editor = workspace
12368        .update(cx, |_, window, cx| {
12369            cx.new(|cx| {
12370                Editor::new(
12371                    EditorMode::Full {
12372                        scale_ui_elements_with_buffer_font_size: false,
12373                        show_active_line_background: false,
12374                        sized_by_content: false,
12375                    },
12376                    multi_buffer.clone(),
12377                    Some(project.clone()),
12378                    window,
12379                    cx,
12380                )
12381            })
12382        })
12383        .unwrap();
12384
12385    let pane = workspace
12386        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12387        .unwrap();
12388    pane.update_in(cx, |pane, window, cx| {
12389        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12390    });
12391
12392    let fake_server = fake_servers.next().await.unwrap();
12393
12394    editor.update_in(cx, |editor, window, cx| {
12395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12396            s.select_ranges([
12397                Point::new(1, 11)..Point::new(1, 11),
12398                Point::new(7, 11)..Point::new(7, 11),
12399            ])
12400        });
12401
12402        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12403    });
12404
12405    editor.update_in(cx, |editor, window, cx| {
12406        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12407    });
12408
12409    fake_server
12410        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12411            let completion_item = lsp::CompletionItem {
12412                label: "saturating_sub()".into(),
12413                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12414                    lsp::InsertReplaceEdit {
12415                        new_text: "saturating_sub()".to_owned(),
12416                        insert: lsp::Range::new(
12417                            lsp::Position::new(7, 7),
12418                            lsp::Position::new(7, 11),
12419                        ),
12420                        replace: lsp::Range::new(
12421                            lsp::Position::new(7, 7),
12422                            lsp::Position::new(7, 13),
12423                        ),
12424                    },
12425                )),
12426                ..lsp::CompletionItem::default()
12427            };
12428
12429            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12430        })
12431        .next()
12432        .await
12433        .unwrap();
12434
12435    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12436        .await;
12437
12438    editor
12439        .update_in(cx, |editor, window, cx| {
12440            editor
12441                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12442                .unwrap()
12443        })
12444        .await
12445        .unwrap();
12446
12447    editor.update(cx, |editor, cx| {
12448        assert_text_with_selections(editor, expected_multibuffer, cx);
12449    })
12450}
12451
12452#[gpui::test]
12453async fn test_completion(cx: &mut TestAppContext) {
12454    init_test(cx, |_| {});
12455
12456    let mut cx = EditorLspTestContext::new_rust(
12457        lsp::ServerCapabilities {
12458            completion_provider: Some(lsp::CompletionOptions {
12459                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12460                resolve_provider: Some(true),
12461                ..Default::default()
12462            }),
12463            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12464            ..Default::default()
12465        },
12466        cx,
12467    )
12468    .await;
12469    let counter = Arc::new(AtomicUsize::new(0));
12470
12471    cx.set_state(indoc! {"
12472        oneˇ
12473        two
12474        three
12475    "});
12476    cx.simulate_keystroke(".");
12477    handle_completion_request(
12478        indoc! {"
12479            one.|<>
12480            two
12481            three
12482        "},
12483        vec!["first_completion", "second_completion"],
12484        true,
12485        counter.clone(),
12486        &mut cx,
12487    )
12488    .await;
12489    cx.condition(|editor, _| editor.context_menu_visible())
12490        .await;
12491    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12492
12493    let _handler = handle_signature_help_request(
12494        &mut cx,
12495        lsp::SignatureHelp {
12496            signatures: vec![lsp::SignatureInformation {
12497                label: "test signature".to_string(),
12498                documentation: None,
12499                parameters: Some(vec![lsp::ParameterInformation {
12500                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12501                    documentation: None,
12502                }]),
12503                active_parameter: None,
12504            }],
12505            active_signature: None,
12506            active_parameter: None,
12507        },
12508    );
12509    cx.update_editor(|editor, window, cx| {
12510        assert!(
12511            !editor.signature_help_state.is_shown(),
12512            "No signature help was called for"
12513        );
12514        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12515    });
12516    cx.run_until_parked();
12517    cx.update_editor(|editor, _, _| {
12518        assert!(
12519            !editor.signature_help_state.is_shown(),
12520            "No signature help should be shown when completions menu is open"
12521        );
12522    });
12523
12524    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12525        editor.context_menu_next(&Default::default(), window, cx);
12526        editor
12527            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12528            .unwrap()
12529    });
12530    cx.assert_editor_state(indoc! {"
12531        one.second_completionˇ
12532        two
12533        three
12534    "});
12535
12536    handle_resolve_completion_request(
12537        &mut cx,
12538        Some(vec![
12539            (
12540                //This overlaps with the primary completion edit which is
12541                //misbehavior from the LSP spec, test that we filter it out
12542                indoc! {"
12543                    one.second_ˇcompletion
12544                    two
12545                    threeˇ
12546                "},
12547                "overlapping additional edit",
12548            ),
12549            (
12550                indoc! {"
12551                    one.second_completion
12552                    two
12553                    threeˇ
12554                "},
12555                "\nadditional edit",
12556            ),
12557        ]),
12558    )
12559    .await;
12560    apply_additional_edits.await.unwrap();
12561    cx.assert_editor_state(indoc! {"
12562        one.second_completionˇ
12563        two
12564        three
12565        additional edit
12566    "});
12567
12568    cx.set_state(indoc! {"
12569        one.second_completion
12570        twoˇ
12571        threeˇ
12572        additional edit
12573    "});
12574    cx.simulate_keystroke(" ");
12575    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12576    cx.simulate_keystroke("s");
12577    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12578
12579    cx.assert_editor_state(indoc! {"
12580        one.second_completion
12581        two sˇ
12582        three sˇ
12583        additional edit
12584    "});
12585    handle_completion_request(
12586        indoc! {"
12587            one.second_completion
12588            two s
12589            three <s|>
12590            additional edit
12591        "},
12592        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12593        true,
12594        counter.clone(),
12595        &mut cx,
12596    )
12597    .await;
12598    cx.condition(|editor, _| editor.context_menu_visible())
12599        .await;
12600    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12601
12602    cx.simulate_keystroke("i");
12603
12604    handle_completion_request(
12605        indoc! {"
12606            one.second_completion
12607            two si
12608            three <si|>
12609            additional edit
12610        "},
12611        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12612        true,
12613        counter.clone(),
12614        &mut cx,
12615    )
12616    .await;
12617    cx.condition(|editor, _| editor.context_menu_visible())
12618        .await;
12619    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12620
12621    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12622        editor
12623            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12624            .unwrap()
12625    });
12626    cx.assert_editor_state(indoc! {"
12627        one.second_completion
12628        two sixth_completionˇ
12629        three sixth_completionˇ
12630        additional edit
12631    "});
12632
12633    apply_additional_edits.await.unwrap();
12634
12635    update_test_language_settings(&mut cx, |settings| {
12636        settings.defaults.show_completions_on_input = Some(false);
12637    });
12638    cx.set_state("editorˇ");
12639    cx.simulate_keystroke(".");
12640    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12641    cx.simulate_keystrokes("c l o");
12642    cx.assert_editor_state("editor.cloˇ");
12643    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12644    cx.update_editor(|editor, window, cx| {
12645        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12646    });
12647    handle_completion_request(
12648        "editor.<clo|>",
12649        vec!["close", "clobber"],
12650        true,
12651        counter.clone(),
12652        &mut cx,
12653    )
12654    .await;
12655    cx.condition(|editor, _| editor.context_menu_visible())
12656        .await;
12657    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12658
12659    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12660        editor
12661            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12662            .unwrap()
12663    });
12664    cx.assert_editor_state("editor.clobberˇ");
12665    handle_resolve_completion_request(&mut cx, None).await;
12666    apply_additional_edits.await.unwrap();
12667}
12668
12669#[gpui::test]
12670async fn test_completion_reuse(cx: &mut TestAppContext) {
12671    init_test(cx, |_| {});
12672
12673    let mut cx = EditorLspTestContext::new_rust(
12674        lsp::ServerCapabilities {
12675            completion_provider: Some(lsp::CompletionOptions {
12676                trigger_characters: Some(vec![".".to_string()]),
12677                ..Default::default()
12678            }),
12679            ..Default::default()
12680        },
12681        cx,
12682    )
12683    .await;
12684
12685    let counter = Arc::new(AtomicUsize::new(0));
12686    cx.set_state("objˇ");
12687    cx.simulate_keystroke(".");
12688
12689    // Initial completion request returns complete results
12690    let is_incomplete = false;
12691    handle_completion_request(
12692        "obj.|<>",
12693        vec!["a", "ab", "abc"],
12694        is_incomplete,
12695        counter.clone(),
12696        &mut cx,
12697    )
12698    .await;
12699    cx.run_until_parked();
12700    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12701    cx.assert_editor_state("obj.ˇ");
12702    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12703
12704    // Type "a" - filters existing completions
12705    cx.simulate_keystroke("a");
12706    cx.run_until_parked();
12707    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12708    cx.assert_editor_state("obj.aˇ");
12709    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12710
12711    // Type "b" - filters existing completions
12712    cx.simulate_keystroke("b");
12713    cx.run_until_parked();
12714    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12715    cx.assert_editor_state("obj.abˇ");
12716    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12717
12718    // Type "c" - filters existing completions
12719    cx.simulate_keystroke("c");
12720    cx.run_until_parked();
12721    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12722    cx.assert_editor_state("obj.abcˇ");
12723    check_displayed_completions(vec!["abc"], &mut cx);
12724
12725    // Backspace to delete "c" - filters existing completions
12726    cx.update_editor(|editor, window, cx| {
12727        editor.backspace(&Backspace, window, cx);
12728    });
12729    cx.run_until_parked();
12730    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12731    cx.assert_editor_state("obj.abˇ");
12732    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12733
12734    // Moving cursor to the left dismisses menu.
12735    cx.update_editor(|editor, window, cx| {
12736        editor.move_left(&MoveLeft, window, cx);
12737    });
12738    cx.run_until_parked();
12739    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12740    cx.assert_editor_state("obj.aˇb");
12741    cx.update_editor(|editor, _, _| {
12742        assert_eq!(editor.context_menu_visible(), false);
12743    });
12744
12745    // Type "b" - new request
12746    cx.simulate_keystroke("b");
12747    let is_incomplete = false;
12748    handle_completion_request(
12749        "obj.<ab|>a",
12750        vec!["ab", "abc"],
12751        is_incomplete,
12752        counter.clone(),
12753        &mut cx,
12754    )
12755    .await;
12756    cx.run_until_parked();
12757    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12758    cx.assert_editor_state("obj.abˇb");
12759    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12760
12761    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12762    cx.update_editor(|editor, window, cx| {
12763        editor.backspace(&Backspace, window, cx);
12764    });
12765    let is_incomplete = false;
12766    handle_completion_request(
12767        "obj.<a|>b",
12768        vec!["a", "ab", "abc"],
12769        is_incomplete,
12770        counter.clone(),
12771        &mut cx,
12772    )
12773    .await;
12774    cx.run_until_parked();
12775    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12776    cx.assert_editor_state("obj.aˇb");
12777    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12778
12779    // Backspace to delete "a" - dismisses menu.
12780    cx.update_editor(|editor, window, cx| {
12781        editor.backspace(&Backspace, window, cx);
12782    });
12783    cx.run_until_parked();
12784    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12785    cx.assert_editor_state("obj.ˇb");
12786    cx.update_editor(|editor, _, _| {
12787        assert_eq!(editor.context_menu_visible(), false);
12788    });
12789}
12790
12791#[gpui::test]
12792async fn test_word_completion(cx: &mut TestAppContext) {
12793    let lsp_fetch_timeout_ms = 10;
12794    init_test(cx, |language_settings| {
12795        language_settings.defaults.completions = Some(CompletionSettings {
12796            words: WordsCompletionMode::Fallback,
12797            lsp: true,
12798            lsp_fetch_timeout_ms: 10,
12799            lsp_insert_mode: LspInsertMode::Insert,
12800        });
12801    });
12802
12803    let mut cx = EditorLspTestContext::new_rust(
12804        lsp::ServerCapabilities {
12805            completion_provider: Some(lsp::CompletionOptions {
12806                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12807                ..lsp::CompletionOptions::default()
12808            }),
12809            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12810            ..lsp::ServerCapabilities::default()
12811        },
12812        cx,
12813    )
12814    .await;
12815
12816    let throttle_completions = Arc::new(AtomicBool::new(false));
12817
12818    let lsp_throttle_completions = throttle_completions.clone();
12819    let _completion_requests_handler =
12820        cx.lsp
12821            .server
12822            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12823                let lsp_throttle_completions = lsp_throttle_completions.clone();
12824                let cx = cx.clone();
12825                async move {
12826                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12827                        cx.background_executor()
12828                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12829                            .await;
12830                    }
12831                    Ok(Some(lsp::CompletionResponse::Array(vec![
12832                        lsp::CompletionItem {
12833                            label: "first".into(),
12834                            ..lsp::CompletionItem::default()
12835                        },
12836                        lsp::CompletionItem {
12837                            label: "last".into(),
12838                            ..lsp::CompletionItem::default()
12839                        },
12840                    ])))
12841                }
12842            });
12843
12844    cx.set_state(indoc! {"
12845        oneˇ
12846        two
12847        three
12848    "});
12849    cx.simulate_keystroke(".");
12850    cx.executor().run_until_parked();
12851    cx.condition(|editor, _| editor.context_menu_visible())
12852        .await;
12853    cx.update_editor(|editor, window, cx| {
12854        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12855        {
12856            assert_eq!(
12857                completion_menu_entries(&menu),
12858                &["first", "last"],
12859                "When LSP server is fast to reply, no fallback word completions are used"
12860            );
12861        } else {
12862            panic!("expected completion menu to be open");
12863        }
12864        editor.cancel(&Cancel, window, cx);
12865    });
12866    cx.executor().run_until_parked();
12867    cx.condition(|editor, _| !editor.context_menu_visible())
12868        .await;
12869
12870    throttle_completions.store(true, atomic::Ordering::Release);
12871    cx.simulate_keystroke(".");
12872    cx.executor()
12873        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12874    cx.executor().run_until_parked();
12875    cx.condition(|editor, _| editor.context_menu_visible())
12876        .await;
12877    cx.update_editor(|editor, _, _| {
12878        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12879        {
12880            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12881                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12882        } else {
12883            panic!("expected completion menu to be open");
12884        }
12885    });
12886}
12887
12888#[gpui::test]
12889async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12890    init_test(cx, |language_settings| {
12891        language_settings.defaults.completions = Some(CompletionSettings {
12892            words: WordsCompletionMode::Enabled,
12893            lsp: true,
12894            lsp_fetch_timeout_ms: 0,
12895            lsp_insert_mode: LspInsertMode::Insert,
12896        });
12897    });
12898
12899    let mut cx = EditorLspTestContext::new_rust(
12900        lsp::ServerCapabilities {
12901            completion_provider: Some(lsp::CompletionOptions {
12902                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12903                ..lsp::CompletionOptions::default()
12904            }),
12905            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12906            ..lsp::ServerCapabilities::default()
12907        },
12908        cx,
12909    )
12910    .await;
12911
12912    let _completion_requests_handler =
12913        cx.lsp
12914            .server
12915            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12916                Ok(Some(lsp::CompletionResponse::Array(vec![
12917                    lsp::CompletionItem {
12918                        label: "first".into(),
12919                        ..lsp::CompletionItem::default()
12920                    },
12921                    lsp::CompletionItem {
12922                        label: "last".into(),
12923                        ..lsp::CompletionItem::default()
12924                    },
12925                ])))
12926            });
12927
12928    cx.set_state(indoc! {"ˇ
12929        first
12930        last
12931        second
12932    "});
12933    cx.simulate_keystroke(".");
12934    cx.executor().run_until_parked();
12935    cx.condition(|editor, _| editor.context_menu_visible())
12936        .await;
12937    cx.update_editor(|editor, _, _| {
12938        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12939        {
12940            assert_eq!(
12941                completion_menu_entries(&menu),
12942                &["first", "last", "second"],
12943                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12944            );
12945        } else {
12946            panic!("expected completion menu to be open");
12947        }
12948    });
12949}
12950
12951#[gpui::test]
12952async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12953    init_test(cx, |language_settings| {
12954        language_settings.defaults.completions = Some(CompletionSettings {
12955            words: WordsCompletionMode::Disabled,
12956            lsp: true,
12957            lsp_fetch_timeout_ms: 0,
12958            lsp_insert_mode: LspInsertMode::Insert,
12959        });
12960    });
12961
12962    let mut cx = EditorLspTestContext::new_rust(
12963        lsp::ServerCapabilities {
12964            completion_provider: Some(lsp::CompletionOptions {
12965                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12966                ..lsp::CompletionOptions::default()
12967            }),
12968            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12969            ..lsp::ServerCapabilities::default()
12970        },
12971        cx,
12972    )
12973    .await;
12974
12975    let _completion_requests_handler =
12976        cx.lsp
12977            .server
12978            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12979                panic!("LSP completions should not be queried when dealing with word completions")
12980            });
12981
12982    cx.set_state(indoc! {"ˇ
12983        first
12984        last
12985        second
12986    "});
12987    cx.update_editor(|editor, window, cx| {
12988        editor.show_word_completions(&ShowWordCompletions, window, cx);
12989    });
12990    cx.executor().run_until_parked();
12991    cx.condition(|editor, _| editor.context_menu_visible())
12992        .await;
12993    cx.update_editor(|editor, _, _| {
12994        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12995        {
12996            assert_eq!(
12997                completion_menu_entries(&menu),
12998                &["first", "last", "second"],
12999                "`ShowWordCompletions` action should show word completions"
13000            );
13001        } else {
13002            panic!("expected completion menu to be open");
13003        }
13004    });
13005
13006    cx.simulate_keystroke("l");
13007    cx.executor().run_until_parked();
13008    cx.condition(|editor, _| editor.context_menu_visible())
13009        .await;
13010    cx.update_editor(|editor, _, _| {
13011        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13012        {
13013            assert_eq!(
13014                completion_menu_entries(&menu),
13015                &["last"],
13016                "After showing word completions, further editing should filter them and not query the LSP"
13017            );
13018        } else {
13019            panic!("expected completion menu to be open");
13020        }
13021    });
13022}
13023
13024#[gpui::test]
13025async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13026    init_test(cx, |language_settings| {
13027        language_settings.defaults.completions = Some(CompletionSettings {
13028            words: WordsCompletionMode::Fallback,
13029            lsp: false,
13030            lsp_fetch_timeout_ms: 0,
13031            lsp_insert_mode: LspInsertMode::Insert,
13032        });
13033    });
13034
13035    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13036
13037    cx.set_state(indoc! {"ˇ
13038        0_usize
13039        let
13040        33
13041        4.5f32
13042    "});
13043    cx.update_editor(|editor, window, cx| {
13044        editor.show_completions(&ShowCompletions::default(), window, cx);
13045    });
13046    cx.executor().run_until_parked();
13047    cx.condition(|editor, _| editor.context_menu_visible())
13048        .await;
13049    cx.update_editor(|editor, window, cx| {
13050        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13051        {
13052            assert_eq!(
13053                completion_menu_entries(&menu),
13054                &["let"],
13055                "With no digits in the completion query, no digits should be in the word completions"
13056            );
13057        } else {
13058            panic!("expected completion menu to be open");
13059        }
13060        editor.cancel(&Cancel, window, cx);
13061    });
13062
13063    cx.set_state(indoc! {"13064        0_usize
13065        let
13066        3
13067        33.35f32
13068    "});
13069    cx.update_editor(|editor, window, cx| {
13070        editor.show_completions(&ShowCompletions::default(), window, cx);
13071    });
13072    cx.executor().run_until_parked();
13073    cx.condition(|editor, _| editor.context_menu_visible())
13074        .await;
13075    cx.update_editor(|editor, _, _| {
13076        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13077        {
13078            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13079                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13080        } else {
13081            panic!("expected completion menu to be open");
13082        }
13083    });
13084}
13085
13086fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13087    let position = || lsp::Position {
13088        line: params.text_document_position.position.line,
13089        character: params.text_document_position.position.character,
13090    };
13091    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13092        range: lsp::Range {
13093            start: position(),
13094            end: position(),
13095        },
13096        new_text: text.to_string(),
13097    }))
13098}
13099
13100#[gpui::test]
13101async fn test_multiline_completion(cx: &mut TestAppContext) {
13102    init_test(cx, |_| {});
13103
13104    let fs = FakeFs::new(cx.executor());
13105    fs.insert_tree(
13106        path!("/a"),
13107        json!({
13108            "main.ts": "a",
13109        }),
13110    )
13111    .await;
13112
13113    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13114    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13115    let typescript_language = Arc::new(Language::new(
13116        LanguageConfig {
13117            name: "TypeScript".into(),
13118            matcher: LanguageMatcher {
13119                path_suffixes: vec!["ts".to_string()],
13120                ..LanguageMatcher::default()
13121            },
13122            line_comments: vec!["// ".into()],
13123            ..LanguageConfig::default()
13124        },
13125        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13126    ));
13127    language_registry.add(typescript_language.clone());
13128    let mut fake_servers = language_registry.register_fake_lsp(
13129        "TypeScript",
13130        FakeLspAdapter {
13131            capabilities: lsp::ServerCapabilities {
13132                completion_provider: Some(lsp::CompletionOptions {
13133                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13134                    ..lsp::CompletionOptions::default()
13135                }),
13136                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13137                ..lsp::ServerCapabilities::default()
13138            },
13139            // Emulate vtsls label generation
13140            label_for_completion: Some(Box::new(|item, _| {
13141                let text = if let Some(description) = item
13142                    .label_details
13143                    .as_ref()
13144                    .and_then(|label_details| label_details.description.as_ref())
13145                {
13146                    format!("{} {}", item.label, description)
13147                } else if let Some(detail) = &item.detail {
13148                    format!("{} {}", item.label, detail)
13149                } else {
13150                    item.label.clone()
13151                };
13152                let len = text.len();
13153                Some(language::CodeLabel {
13154                    text,
13155                    runs: Vec::new(),
13156                    filter_range: 0..len,
13157                })
13158            })),
13159            ..FakeLspAdapter::default()
13160        },
13161    );
13162    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13163    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13164    let worktree_id = workspace
13165        .update(cx, |workspace, _window, cx| {
13166            workspace.project().update(cx, |project, cx| {
13167                project.worktrees(cx).next().unwrap().read(cx).id()
13168            })
13169        })
13170        .unwrap();
13171    let _buffer = project
13172        .update(cx, |project, cx| {
13173            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13174        })
13175        .await
13176        .unwrap();
13177    let editor = workspace
13178        .update(cx, |workspace, window, cx| {
13179            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13180        })
13181        .unwrap()
13182        .await
13183        .unwrap()
13184        .downcast::<Editor>()
13185        .unwrap();
13186    let fake_server = fake_servers.next().await.unwrap();
13187
13188    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13189    let multiline_label_2 = "a\nb\nc\n";
13190    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13191    let multiline_description = "d\ne\nf\n";
13192    let multiline_detail_2 = "g\nh\ni\n";
13193
13194    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13195        move |params, _| async move {
13196            Ok(Some(lsp::CompletionResponse::Array(vec![
13197                lsp::CompletionItem {
13198                    label: multiline_label.to_string(),
13199                    text_edit: gen_text_edit(&params, "new_text_1"),
13200                    ..lsp::CompletionItem::default()
13201                },
13202                lsp::CompletionItem {
13203                    label: "single line label 1".to_string(),
13204                    detail: Some(multiline_detail.to_string()),
13205                    text_edit: gen_text_edit(&params, "new_text_2"),
13206                    ..lsp::CompletionItem::default()
13207                },
13208                lsp::CompletionItem {
13209                    label: "single line label 2".to_string(),
13210                    label_details: Some(lsp::CompletionItemLabelDetails {
13211                        description: Some(multiline_description.to_string()),
13212                        detail: None,
13213                    }),
13214                    text_edit: gen_text_edit(&params, "new_text_2"),
13215                    ..lsp::CompletionItem::default()
13216                },
13217                lsp::CompletionItem {
13218                    label: multiline_label_2.to_string(),
13219                    detail: Some(multiline_detail_2.to_string()),
13220                    text_edit: gen_text_edit(&params, "new_text_3"),
13221                    ..lsp::CompletionItem::default()
13222                },
13223                lsp::CompletionItem {
13224                    label: "Label with many     spaces and \t but without newlines".to_string(),
13225                    detail: Some(
13226                        "Details with many     spaces and \t but without newlines".to_string(),
13227                    ),
13228                    text_edit: gen_text_edit(&params, "new_text_4"),
13229                    ..lsp::CompletionItem::default()
13230                },
13231            ])))
13232        },
13233    );
13234
13235    editor.update_in(cx, |editor, window, cx| {
13236        cx.focus_self(window);
13237        editor.move_to_end(&MoveToEnd, window, cx);
13238        editor.handle_input(".", window, cx);
13239    });
13240    cx.run_until_parked();
13241    completion_handle.next().await.unwrap();
13242
13243    editor.update(cx, |editor, _| {
13244        assert!(editor.context_menu_visible());
13245        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13246        {
13247            let completion_labels = menu
13248                .completions
13249                .borrow()
13250                .iter()
13251                .map(|c| c.label.text.clone())
13252                .collect::<Vec<_>>();
13253            assert_eq!(
13254                completion_labels,
13255                &[
13256                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13257                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13258                    "single line label 2 d e f ",
13259                    "a b c g h i ",
13260                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13261                ],
13262                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13263            );
13264
13265            for completion in menu
13266                .completions
13267                .borrow()
13268                .iter() {
13269                    assert_eq!(
13270                        completion.label.filter_range,
13271                        0..completion.label.text.len(),
13272                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13273                    );
13274                }
13275        } else {
13276            panic!("expected completion menu to be open");
13277        }
13278    });
13279}
13280
13281#[gpui::test]
13282async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13283    init_test(cx, |_| {});
13284    let mut cx = EditorLspTestContext::new_rust(
13285        lsp::ServerCapabilities {
13286            completion_provider: Some(lsp::CompletionOptions {
13287                trigger_characters: Some(vec![".".to_string()]),
13288                ..Default::default()
13289            }),
13290            ..Default::default()
13291        },
13292        cx,
13293    )
13294    .await;
13295    cx.lsp
13296        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13297            Ok(Some(lsp::CompletionResponse::Array(vec![
13298                lsp::CompletionItem {
13299                    label: "first".into(),
13300                    ..Default::default()
13301                },
13302                lsp::CompletionItem {
13303                    label: "last".into(),
13304                    ..Default::default()
13305                },
13306            ])))
13307        });
13308    cx.set_state("variableˇ");
13309    cx.simulate_keystroke(".");
13310    cx.executor().run_until_parked();
13311
13312    cx.update_editor(|editor, _, _| {
13313        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13314        {
13315            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13316        } else {
13317            panic!("expected completion menu to be open");
13318        }
13319    });
13320
13321    cx.update_editor(|editor, window, cx| {
13322        editor.move_page_down(&MovePageDown::default(), window, cx);
13323        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13324        {
13325            assert!(
13326                menu.selected_item == 1,
13327                "expected PageDown to select the last item from the context menu"
13328            );
13329        } else {
13330            panic!("expected completion menu to stay open after PageDown");
13331        }
13332    });
13333
13334    cx.update_editor(|editor, window, cx| {
13335        editor.move_page_up(&MovePageUp::default(), window, cx);
13336        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13337        {
13338            assert!(
13339                menu.selected_item == 0,
13340                "expected PageUp to select the first item from the context menu"
13341            );
13342        } else {
13343            panic!("expected completion menu to stay open after PageUp");
13344        }
13345    });
13346}
13347
13348#[gpui::test]
13349async fn test_as_is_completions(cx: &mut TestAppContext) {
13350    init_test(cx, |_| {});
13351    let mut cx = EditorLspTestContext::new_rust(
13352        lsp::ServerCapabilities {
13353            completion_provider: Some(lsp::CompletionOptions {
13354                ..Default::default()
13355            }),
13356            ..Default::default()
13357        },
13358        cx,
13359    )
13360    .await;
13361    cx.lsp
13362        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13363            Ok(Some(lsp::CompletionResponse::Array(vec![
13364                lsp::CompletionItem {
13365                    label: "unsafe".into(),
13366                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13367                        range: lsp::Range {
13368                            start: lsp::Position {
13369                                line: 1,
13370                                character: 2,
13371                            },
13372                            end: lsp::Position {
13373                                line: 1,
13374                                character: 3,
13375                            },
13376                        },
13377                        new_text: "unsafe".to_string(),
13378                    })),
13379                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13380                    ..Default::default()
13381                },
13382            ])))
13383        });
13384    cx.set_state("fn a() {}\n");
13385    cx.executor().run_until_parked();
13386    cx.update_editor(|editor, window, cx| {
13387        editor.show_completions(
13388            &ShowCompletions {
13389                trigger: Some("\n".into()),
13390            },
13391            window,
13392            cx,
13393        );
13394    });
13395    cx.executor().run_until_parked();
13396
13397    cx.update_editor(|editor, window, cx| {
13398        editor.confirm_completion(&Default::default(), window, cx)
13399    });
13400    cx.executor().run_until_parked();
13401    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13402}
13403
13404#[gpui::test]
13405async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13406    init_test(cx, |_| {});
13407    let language =
13408        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13409    let mut cx = EditorLspTestContext::new(
13410        language,
13411        lsp::ServerCapabilities {
13412            completion_provider: Some(lsp::CompletionOptions {
13413                ..lsp::CompletionOptions::default()
13414            }),
13415            ..lsp::ServerCapabilities::default()
13416        },
13417        cx,
13418    )
13419    .await;
13420
13421    cx.set_state(
13422        "#ifndef BAR_H
13423#define BAR_H
13424
13425#include <stdbool.h>
13426
13427int fn_branch(bool do_branch1, bool do_branch2);
13428
13429#endif // BAR_H
13430ˇ",
13431    );
13432    cx.executor().run_until_parked();
13433    cx.update_editor(|editor, window, cx| {
13434        editor.handle_input("#", window, cx);
13435    });
13436    cx.executor().run_until_parked();
13437    cx.update_editor(|editor, window, cx| {
13438        editor.handle_input("i", window, cx);
13439    });
13440    cx.executor().run_until_parked();
13441    cx.update_editor(|editor, window, cx| {
13442        editor.handle_input("n", window, cx);
13443    });
13444    cx.executor().run_until_parked();
13445    cx.assert_editor_state(
13446        "#ifndef BAR_H
13447#define BAR_H
13448
13449#include <stdbool.h>
13450
13451int fn_branch(bool do_branch1, bool do_branch2);
13452
13453#endif // BAR_H
13454#inˇ",
13455    );
13456
13457    cx.lsp
13458        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13459            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13460                is_incomplete: false,
13461                item_defaults: None,
13462                items: vec![lsp::CompletionItem {
13463                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13464                    label_details: Some(lsp::CompletionItemLabelDetails {
13465                        detail: Some("header".to_string()),
13466                        description: None,
13467                    }),
13468                    label: " include".to_string(),
13469                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13470                        range: lsp::Range {
13471                            start: lsp::Position {
13472                                line: 8,
13473                                character: 1,
13474                            },
13475                            end: lsp::Position {
13476                                line: 8,
13477                                character: 1,
13478                            },
13479                        },
13480                        new_text: "include \"$0\"".to_string(),
13481                    })),
13482                    sort_text: Some("40b67681include".to_string()),
13483                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13484                    filter_text: Some("include".to_string()),
13485                    insert_text: Some("include \"$0\"".to_string()),
13486                    ..lsp::CompletionItem::default()
13487                }],
13488            })))
13489        });
13490    cx.update_editor(|editor, window, cx| {
13491        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13492    });
13493    cx.executor().run_until_parked();
13494    cx.update_editor(|editor, window, cx| {
13495        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13496    });
13497    cx.executor().run_until_parked();
13498    cx.assert_editor_state(
13499        "#ifndef BAR_H
13500#define BAR_H
13501
13502#include <stdbool.h>
13503
13504int fn_branch(bool do_branch1, bool do_branch2);
13505
13506#endif // BAR_H
13507#include \"ˇ\"",
13508    );
13509
13510    cx.lsp
13511        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13512            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13513                is_incomplete: true,
13514                item_defaults: None,
13515                items: vec![lsp::CompletionItem {
13516                    kind: Some(lsp::CompletionItemKind::FILE),
13517                    label: "AGL/".to_string(),
13518                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13519                        range: lsp::Range {
13520                            start: lsp::Position {
13521                                line: 8,
13522                                character: 10,
13523                            },
13524                            end: lsp::Position {
13525                                line: 8,
13526                                character: 11,
13527                            },
13528                        },
13529                        new_text: "AGL/".to_string(),
13530                    })),
13531                    sort_text: Some("40b67681AGL/".to_string()),
13532                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13533                    filter_text: Some("AGL/".to_string()),
13534                    insert_text: Some("AGL/".to_string()),
13535                    ..lsp::CompletionItem::default()
13536                }],
13537            })))
13538        });
13539    cx.update_editor(|editor, window, cx| {
13540        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13541    });
13542    cx.executor().run_until_parked();
13543    cx.update_editor(|editor, window, cx| {
13544        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13545    });
13546    cx.executor().run_until_parked();
13547    cx.assert_editor_state(
13548        r##"#ifndef BAR_H
13549#define BAR_H
13550
13551#include <stdbool.h>
13552
13553int fn_branch(bool do_branch1, bool do_branch2);
13554
13555#endif // BAR_H
13556#include "AGL/ˇ"##,
13557    );
13558
13559    cx.update_editor(|editor, window, cx| {
13560        editor.handle_input("\"", window, cx);
13561    });
13562    cx.executor().run_until_parked();
13563    cx.assert_editor_state(
13564        r##"#ifndef BAR_H
13565#define BAR_H
13566
13567#include <stdbool.h>
13568
13569int fn_branch(bool do_branch1, bool do_branch2);
13570
13571#endif // BAR_H
13572#include "AGL/"ˇ"##,
13573    );
13574}
13575
13576#[gpui::test]
13577async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13578    init_test(cx, |_| {});
13579
13580    let mut cx = EditorLspTestContext::new_rust(
13581        lsp::ServerCapabilities {
13582            completion_provider: Some(lsp::CompletionOptions {
13583                trigger_characters: Some(vec![".".to_string()]),
13584                resolve_provider: Some(true),
13585                ..Default::default()
13586            }),
13587            ..Default::default()
13588        },
13589        cx,
13590    )
13591    .await;
13592
13593    cx.set_state("fn main() { let a = 2ˇ; }");
13594    cx.simulate_keystroke(".");
13595    let completion_item = lsp::CompletionItem {
13596        label: "Some".into(),
13597        kind: Some(lsp::CompletionItemKind::SNIPPET),
13598        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13599        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13600            kind: lsp::MarkupKind::Markdown,
13601            value: "```rust\nSome(2)\n```".to_string(),
13602        })),
13603        deprecated: Some(false),
13604        sort_text: Some("Some".to_string()),
13605        filter_text: Some("Some".to_string()),
13606        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13607        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13608            range: lsp::Range {
13609                start: lsp::Position {
13610                    line: 0,
13611                    character: 22,
13612                },
13613                end: lsp::Position {
13614                    line: 0,
13615                    character: 22,
13616                },
13617            },
13618            new_text: "Some(2)".to_string(),
13619        })),
13620        additional_text_edits: Some(vec![lsp::TextEdit {
13621            range: lsp::Range {
13622                start: lsp::Position {
13623                    line: 0,
13624                    character: 20,
13625                },
13626                end: lsp::Position {
13627                    line: 0,
13628                    character: 22,
13629                },
13630            },
13631            new_text: "".to_string(),
13632        }]),
13633        ..Default::default()
13634    };
13635
13636    let closure_completion_item = completion_item.clone();
13637    let counter = Arc::new(AtomicUsize::new(0));
13638    let counter_clone = counter.clone();
13639    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13640        let task_completion_item = closure_completion_item.clone();
13641        counter_clone.fetch_add(1, atomic::Ordering::Release);
13642        async move {
13643            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13644                is_incomplete: true,
13645                item_defaults: None,
13646                items: vec![task_completion_item],
13647            })))
13648        }
13649    });
13650
13651    cx.condition(|editor, _| editor.context_menu_visible())
13652        .await;
13653    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13654    assert!(request.next().await.is_some());
13655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13656
13657    cx.simulate_keystrokes("S o m");
13658    cx.condition(|editor, _| editor.context_menu_visible())
13659        .await;
13660    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13661    assert!(request.next().await.is_some());
13662    assert!(request.next().await.is_some());
13663    assert!(request.next().await.is_some());
13664    request.close();
13665    assert!(request.next().await.is_none());
13666    assert_eq!(
13667        counter.load(atomic::Ordering::Acquire),
13668        4,
13669        "With the completions menu open, only one LSP request should happen per input"
13670    );
13671}
13672
13673#[gpui::test]
13674async fn test_toggle_comment(cx: &mut TestAppContext) {
13675    init_test(cx, |_| {});
13676    let mut cx = EditorTestContext::new(cx).await;
13677    let language = Arc::new(Language::new(
13678        LanguageConfig {
13679            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13680            ..Default::default()
13681        },
13682        Some(tree_sitter_rust::LANGUAGE.into()),
13683    ));
13684    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13685
13686    // If multiple selections intersect a line, the line is only toggled once.
13687    cx.set_state(indoc! {"
13688        fn a() {
13689            «//b();
13690            ˇ»// «c();
13691            //ˇ»  d();
13692        }
13693    "});
13694
13695    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13696
13697    cx.assert_editor_state(indoc! {"
13698        fn a() {
13699            «b();
13700            c();
13701            ˇ» d();
13702        }
13703    "});
13704
13705    // The comment prefix is inserted at the same column for every line in a
13706    // selection.
13707    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13708
13709    cx.assert_editor_state(indoc! {"
13710        fn a() {
13711            // «b();
13712            // c();
13713            ˇ»//  d();
13714        }
13715    "});
13716
13717    // If a selection ends at the beginning of a line, that line is not toggled.
13718    cx.set_selections_state(indoc! {"
13719        fn a() {
13720            // b();
13721            «// c();
13722        ˇ»    //  d();
13723        }
13724    "});
13725
13726    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13727
13728    cx.assert_editor_state(indoc! {"
13729        fn a() {
13730            // b();
13731            «c();
13732        ˇ»    //  d();
13733        }
13734    "});
13735
13736    // If a selection span a single line and is empty, the line is toggled.
13737    cx.set_state(indoc! {"
13738        fn a() {
13739            a();
13740            b();
13741        ˇ
13742        }
13743    "});
13744
13745    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13746
13747    cx.assert_editor_state(indoc! {"
13748        fn a() {
13749            a();
13750            b();
13751        //•ˇ
13752        }
13753    "});
13754
13755    // If a selection span multiple lines, empty lines are not toggled.
13756    cx.set_state(indoc! {"
13757        fn a() {
13758            «a();
13759
13760            c();ˇ»
13761        }
13762    "});
13763
13764    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13765
13766    cx.assert_editor_state(indoc! {"
13767        fn a() {
13768            // «a();
13769
13770            // c();ˇ»
13771        }
13772    "});
13773
13774    // If a selection includes multiple comment prefixes, all lines are uncommented.
13775    cx.set_state(indoc! {"
13776        fn a() {
13777            «// a();
13778            /// b();
13779            //! c();ˇ»
13780        }
13781    "});
13782
13783    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13784
13785    cx.assert_editor_state(indoc! {"
13786        fn a() {
13787            «a();
13788            b();
13789            c();ˇ»
13790        }
13791    "});
13792}
13793
13794#[gpui::test]
13795async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13796    init_test(cx, |_| {});
13797    let mut cx = EditorTestContext::new(cx).await;
13798    let language = Arc::new(Language::new(
13799        LanguageConfig {
13800            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13801            ..Default::default()
13802        },
13803        Some(tree_sitter_rust::LANGUAGE.into()),
13804    ));
13805    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13806
13807    let toggle_comments = &ToggleComments {
13808        advance_downwards: false,
13809        ignore_indent: true,
13810    };
13811
13812    // If multiple selections intersect a line, the line is only toggled once.
13813    cx.set_state(indoc! {"
13814        fn a() {
13815        //    «b();
13816        //    c();
13817        //    ˇ» d();
13818        }
13819    "});
13820
13821    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13822
13823    cx.assert_editor_state(indoc! {"
13824        fn a() {
13825            «b();
13826            c();
13827            ˇ» d();
13828        }
13829    "});
13830
13831    // The comment prefix is inserted at the beginning of each line
13832    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13833
13834    cx.assert_editor_state(indoc! {"
13835        fn a() {
13836        //    «b();
13837        //    c();
13838        //    ˇ» d();
13839        }
13840    "});
13841
13842    // If a selection ends at the beginning of a line, that line is not toggled.
13843    cx.set_selections_state(indoc! {"
13844        fn a() {
13845        //    b();
13846        //    «c();
13847        ˇ»//     d();
13848        }
13849    "});
13850
13851    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13852
13853    cx.assert_editor_state(indoc! {"
13854        fn a() {
13855        //    b();
13856            «c();
13857        ˇ»//     d();
13858        }
13859    "});
13860
13861    // If a selection span a single line and is empty, the line is toggled.
13862    cx.set_state(indoc! {"
13863        fn a() {
13864            a();
13865            b();
13866        ˇ
13867        }
13868    "});
13869
13870    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13871
13872    cx.assert_editor_state(indoc! {"
13873        fn a() {
13874            a();
13875            b();
13876        //ˇ
13877        }
13878    "});
13879
13880    // If a selection span multiple lines, empty lines are not toggled.
13881    cx.set_state(indoc! {"
13882        fn a() {
13883            «a();
13884
13885            c();ˇ»
13886        }
13887    "});
13888
13889    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13890
13891    cx.assert_editor_state(indoc! {"
13892        fn a() {
13893        //    «a();
13894
13895        //    c();ˇ»
13896        }
13897    "});
13898
13899    // If a selection includes multiple comment prefixes, all lines are uncommented.
13900    cx.set_state(indoc! {"
13901        fn a() {
13902        //    «a();
13903        ///    b();
13904        //!    c();ˇ»
13905        }
13906    "});
13907
13908    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13909
13910    cx.assert_editor_state(indoc! {"
13911        fn a() {
13912            «a();
13913            b();
13914            c();ˇ»
13915        }
13916    "});
13917}
13918
13919#[gpui::test]
13920async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13921    init_test(cx, |_| {});
13922
13923    let language = Arc::new(Language::new(
13924        LanguageConfig {
13925            line_comments: vec!["// ".into()],
13926            ..Default::default()
13927        },
13928        Some(tree_sitter_rust::LANGUAGE.into()),
13929    ));
13930
13931    let mut cx = EditorTestContext::new(cx).await;
13932
13933    cx.language_registry().add(language.clone());
13934    cx.update_buffer(|buffer, cx| {
13935        buffer.set_language(Some(language), cx);
13936    });
13937
13938    let toggle_comments = &ToggleComments {
13939        advance_downwards: true,
13940        ignore_indent: false,
13941    };
13942
13943    // Single cursor on one line -> advance
13944    // Cursor moves horizontally 3 characters as well on non-blank line
13945    cx.set_state(indoc!(
13946        "fn a() {
13947             ˇdog();
13948             cat();
13949        }"
13950    ));
13951    cx.update_editor(|editor, window, cx| {
13952        editor.toggle_comments(toggle_comments, window, cx);
13953    });
13954    cx.assert_editor_state(indoc!(
13955        "fn a() {
13956             // dog();
13957             catˇ();
13958        }"
13959    ));
13960
13961    // Single selection on one line -> don't advance
13962    cx.set_state(indoc!(
13963        "fn a() {
13964             «dog()ˇ»;
13965             cat();
13966        }"
13967    ));
13968    cx.update_editor(|editor, window, cx| {
13969        editor.toggle_comments(toggle_comments, window, cx);
13970    });
13971    cx.assert_editor_state(indoc!(
13972        "fn a() {
13973             // «dog()ˇ»;
13974             cat();
13975        }"
13976    ));
13977
13978    // Multiple cursors on one line -> advance
13979    cx.set_state(indoc!(
13980        "fn a() {
13981             ˇdˇog();
13982             cat();
13983        }"
13984    ));
13985    cx.update_editor(|editor, window, cx| {
13986        editor.toggle_comments(toggle_comments, window, cx);
13987    });
13988    cx.assert_editor_state(indoc!(
13989        "fn a() {
13990             // dog();
13991             catˇ(ˇ);
13992        }"
13993    ));
13994
13995    // Multiple cursors on one line, with selection -> don't advance
13996    cx.set_state(indoc!(
13997        "fn a() {
13998             ˇdˇog«()ˇ»;
13999             cat();
14000        }"
14001    ));
14002    cx.update_editor(|editor, window, cx| {
14003        editor.toggle_comments(toggle_comments, window, cx);
14004    });
14005    cx.assert_editor_state(indoc!(
14006        "fn a() {
14007             // ˇdˇog«()ˇ»;
14008             cat();
14009        }"
14010    ));
14011
14012    // Single cursor on one line -> advance
14013    // Cursor moves to column 0 on blank line
14014    cx.set_state(indoc!(
14015        "fn a() {
14016             ˇdog();
14017
14018             cat();
14019        }"
14020    ));
14021    cx.update_editor(|editor, window, cx| {
14022        editor.toggle_comments(toggle_comments, window, cx);
14023    });
14024    cx.assert_editor_state(indoc!(
14025        "fn a() {
14026             // dog();
14027        ˇ
14028             cat();
14029        }"
14030    ));
14031
14032    // Single cursor on one line -> advance
14033    // Cursor starts and ends at column 0
14034    cx.set_state(indoc!(
14035        "fn a() {
14036         ˇ    dog();
14037             cat();
14038        }"
14039    ));
14040    cx.update_editor(|editor, window, cx| {
14041        editor.toggle_comments(toggle_comments, window, cx);
14042    });
14043    cx.assert_editor_state(indoc!(
14044        "fn a() {
14045             // dog();
14046         ˇ    cat();
14047        }"
14048    ));
14049}
14050
14051#[gpui::test]
14052async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14053    init_test(cx, |_| {});
14054
14055    let mut cx = EditorTestContext::new(cx).await;
14056
14057    let html_language = Arc::new(
14058        Language::new(
14059            LanguageConfig {
14060                name: "HTML".into(),
14061                block_comment: Some(BlockCommentConfig {
14062                    start: "<!-- ".into(),
14063                    prefix: "".into(),
14064                    end: " -->".into(),
14065                    tab_size: 0,
14066                }),
14067                ..Default::default()
14068            },
14069            Some(tree_sitter_html::LANGUAGE.into()),
14070        )
14071        .with_injection_query(
14072            r#"
14073            (script_element
14074                (raw_text) @injection.content
14075                (#set! injection.language "javascript"))
14076            "#,
14077        )
14078        .unwrap(),
14079    );
14080
14081    let javascript_language = Arc::new(Language::new(
14082        LanguageConfig {
14083            name: "JavaScript".into(),
14084            line_comments: vec!["// ".into()],
14085            ..Default::default()
14086        },
14087        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14088    ));
14089
14090    cx.language_registry().add(html_language.clone());
14091    cx.language_registry().add(javascript_language.clone());
14092    cx.update_buffer(|buffer, cx| {
14093        buffer.set_language(Some(html_language), cx);
14094    });
14095
14096    // Toggle comments for empty selections
14097    cx.set_state(
14098        &r#"
14099            <p>A</p>ˇ
14100            <p>B</p>ˇ
14101            <p>C</p>ˇ
14102        "#
14103        .unindent(),
14104    );
14105    cx.update_editor(|editor, window, cx| {
14106        editor.toggle_comments(&ToggleComments::default(), window, cx)
14107    });
14108    cx.assert_editor_state(
14109        &r#"
14110            <!-- <p>A</p>ˇ -->
14111            <!-- <p>B</p>ˇ -->
14112            <!-- <p>C</p>ˇ -->
14113        "#
14114        .unindent(),
14115    );
14116    cx.update_editor(|editor, window, cx| {
14117        editor.toggle_comments(&ToggleComments::default(), window, cx)
14118    });
14119    cx.assert_editor_state(
14120        &r#"
14121            <p>A</p>ˇ
14122            <p>B</p>ˇ
14123            <p>C</p>ˇ
14124        "#
14125        .unindent(),
14126    );
14127
14128    // Toggle comments for mixture of empty and non-empty selections, where
14129    // multiple selections occupy a given line.
14130    cx.set_state(
14131        &r#"
14132            <p>A«</p>
14133            <p>ˇ»B</p>ˇ
14134            <p>C«</p>
14135            <p>ˇ»D</p>ˇ
14136        "#
14137        .unindent(),
14138    );
14139
14140    cx.update_editor(|editor, window, cx| {
14141        editor.toggle_comments(&ToggleComments::default(), window, cx)
14142    });
14143    cx.assert_editor_state(
14144        &r#"
14145            <!-- <p>A«</p>
14146            <p>ˇ»B</p>ˇ -->
14147            <!-- <p>C«</p>
14148            <p>ˇ»D</p>ˇ -->
14149        "#
14150        .unindent(),
14151    );
14152    cx.update_editor(|editor, window, cx| {
14153        editor.toggle_comments(&ToggleComments::default(), window, cx)
14154    });
14155    cx.assert_editor_state(
14156        &r#"
14157            <p>A«</p>
14158            <p>ˇ»B</p>ˇ
14159            <p>C«</p>
14160            <p>ˇ»D</p>ˇ
14161        "#
14162        .unindent(),
14163    );
14164
14165    // Toggle comments when different languages are active for different
14166    // selections.
14167    cx.set_state(
14168        &r#"
14169            ˇ<script>
14170                ˇvar x = new Y();
14171            ˇ</script>
14172        "#
14173        .unindent(),
14174    );
14175    cx.executor().run_until_parked();
14176    cx.update_editor(|editor, window, cx| {
14177        editor.toggle_comments(&ToggleComments::default(), window, cx)
14178    });
14179    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14180    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14181    cx.assert_editor_state(
14182        &r#"
14183            <!-- ˇ<script> -->
14184                // ˇvar x = new Y();
14185            <!-- ˇ</script> -->
14186        "#
14187        .unindent(),
14188    );
14189}
14190
14191#[gpui::test]
14192fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14193    init_test(cx, |_| {});
14194
14195    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14196    let multibuffer = cx.new(|cx| {
14197        let mut multibuffer = MultiBuffer::new(ReadWrite);
14198        multibuffer.push_excerpts(
14199            buffer.clone(),
14200            [
14201                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14202                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14203            ],
14204            cx,
14205        );
14206        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14207        multibuffer
14208    });
14209
14210    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14211    editor.update_in(cx, |editor, window, cx| {
14212        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14214            s.select_ranges([
14215                Point::new(0, 0)..Point::new(0, 0),
14216                Point::new(1, 0)..Point::new(1, 0),
14217            ])
14218        });
14219
14220        editor.handle_input("X", window, cx);
14221        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14222        assert_eq!(
14223            editor.selections.ranges(cx),
14224            [
14225                Point::new(0, 1)..Point::new(0, 1),
14226                Point::new(1, 1)..Point::new(1, 1),
14227            ]
14228        );
14229
14230        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14231        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14232            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14233        });
14234        editor.backspace(&Default::default(), window, cx);
14235        assert_eq!(editor.text(cx), "Xa\nbbb");
14236        assert_eq!(
14237            editor.selections.ranges(cx),
14238            [Point::new(1, 0)..Point::new(1, 0)]
14239        );
14240
14241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14242            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14243        });
14244        editor.backspace(&Default::default(), window, cx);
14245        assert_eq!(editor.text(cx), "X\nbb");
14246        assert_eq!(
14247            editor.selections.ranges(cx),
14248            [Point::new(0, 1)..Point::new(0, 1)]
14249        );
14250    });
14251}
14252
14253#[gpui::test]
14254fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14255    init_test(cx, |_| {});
14256
14257    let markers = vec![('[', ']').into(), ('(', ')').into()];
14258    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14259        indoc! {"
14260            [aaaa
14261            (bbbb]
14262            cccc)",
14263        },
14264        markers.clone(),
14265    );
14266    let excerpt_ranges = markers.into_iter().map(|marker| {
14267        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14268        ExcerptRange::new(context.clone())
14269    });
14270    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14271    let multibuffer = cx.new(|cx| {
14272        let mut multibuffer = MultiBuffer::new(ReadWrite);
14273        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14274        multibuffer
14275    });
14276
14277    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14278    editor.update_in(cx, |editor, window, cx| {
14279        let (expected_text, selection_ranges) = marked_text_ranges(
14280            indoc! {"
14281                aaaa
14282                bˇbbb
14283                bˇbbˇb
14284                cccc"
14285            },
14286            true,
14287        );
14288        assert_eq!(editor.text(cx), expected_text);
14289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14290            s.select_ranges(selection_ranges)
14291        });
14292
14293        editor.handle_input("X", window, cx);
14294
14295        let (expected_text, expected_selections) = marked_text_ranges(
14296            indoc! {"
14297                aaaa
14298                bXˇbbXb
14299                bXˇbbXˇb
14300                cccc"
14301            },
14302            false,
14303        );
14304        assert_eq!(editor.text(cx), expected_text);
14305        assert_eq!(editor.selections.ranges(cx), expected_selections);
14306
14307        editor.newline(&Newline, window, cx);
14308        let (expected_text, expected_selections) = marked_text_ranges(
14309            indoc! {"
14310                aaaa
14311                bX
14312                ˇbbX
14313                b
14314                bX
14315                ˇbbX
14316                ˇb
14317                cccc"
14318            },
14319            false,
14320        );
14321        assert_eq!(editor.text(cx), expected_text);
14322        assert_eq!(editor.selections.ranges(cx), expected_selections);
14323    });
14324}
14325
14326#[gpui::test]
14327fn test_refresh_selections(cx: &mut TestAppContext) {
14328    init_test(cx, |_| {});
14329
14330    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14331    let mut excerpt1_id = None;
14332    let multibuffer = cx.new(|cx| {
14333        let mut multibuffer = MultiBuffer::new(ReadWrite);
14334        excerpt1_id = multibuffer
14335            .push_excerpts(
14336                buffer.clone(),
14337                [
14338                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14339                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14340                ],
14341                cx,
14342            )
14343            .into_iter()
14344            .next();
14345        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14346        multibuffer
14347    });
14348
14349    let editor = cx.add_window(|window, cx| {
14350        let mut editor = build_editor(multibuffer.clone(), window, cx);
14351        let snapshot = editor.snapshot(window, cx);
14352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14353            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14354        });
14355        editor.begin_selection(
14356            Point::new(2, 1).to_display_point(&snapshot),
14357            true,
14358            1,
14359            window,
14360            cx,
14361        );
14362        assert_eq!(
14363            editor.selections.ranges(cx),
14364            [
14365                Point::new(1, 3)..Point::new(1, 3),
14366                Point::new(2, 1)..Point::new(2, 1),
14367            ]
14368        );
14369        editor
14370    });
14371
14372    // Refreshing selections is a no-op when excerpts haven't changed.
14373    _ = editor.update(cx, |editor, window, cx| {
14374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14375        assert_eq!(
14376            editor.selections.ranges(cx),
14377            [
14378                Point::new(1, 3)..Point::new(1, 3),
14379                Point::new(2, 1)..Point::new(2, 1),
14380            ]
14381        );
14382    });
14383
14384    multibuffer.update(cx, |multibuffer, cx| {
14385        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14386    });
14387    _ = editor.update(cx, |editor, window, cx| {
14388        // Removing an excerpt causes the first selection to become degenerate.
14389        assert_eq!(
14390            editor.selections.ranges(cx),
14391            [
14392                Point::new(0, 0)..Point::new(0, 0),
14393                Point::new(0, 1)..Point::new(0, 1)
14394            ]
14395        );
14396
14397        // Refreshing selections will relocate the first selection to the original buffer
14398        // location.
14399        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14400        assert_eq!(
14401            editor.selections.ranges(cx),
14402            [
14403                Point::new(0, 1)..Point::new(0, 1),
14404                Point::new(0, 3)..Point::new(0, 3)
14405            ]
14406        );
14407        assert!(editor.selections.pending_anchor().is_some());
14408    });
14409}
14410
14411#[gpui::test]
14412fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14413    init_test(cx, |_| {});
14414
14415    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14416    let mut excerpt1_id = None;
14417    let multibuffer = cx.new(|cx| {
14418        let mut multibuffer = MultiBuffer::new(ReadWrite);
14419        excerpt1_id = multibuffer
14420            .push_excerpts(
14421                buffer.clone(),
14422                [
14423                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14424                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14425                ],
14426                cx,
14427            )
14428            .into_iter()
14429            .next();
14430        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14431        multibuffer
14432    });
14433
14434    let editor = cx.add_window(|window, cx| {
14435        let mut editor = build_editor(multibuffer.clone(), window, cx);
14436        let snapshot = editor.snapshot(window, cx);
14437        editor.begin_selection(
14438            Point::new(1, 3).to_display_point(&snapshot),
14439            false,
14440            1,
14441            window,
14442            cx,
14443        );
14444        assert_eq!(
14445            editor.selections.ranges(cx),
14446            [Point::new(1, 3)..Point::new(1, 3)]
14447        );
14448        editor
14449    });
14450
14451    multibuffer.update(cx, |multibuffer, cx| {
14452        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14453    });
14454    _ = editor.update(cx, |editor, window, cx| {
14455        assert_eq!(
14456            editor.selections.ranges(cx),
14457            [Point::new(0, 0)..Point::new(0, 0)]
14458        );
14459
14460        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14462        assert_eq!(
14463            editor.selections.ranges(cx),
14464            [Point::new(0, 3)..Point::new(0, 3)]
14465        );
14466        assert!(editor.selections.pending_anchor().is_some());
14467    });
14468}
14469
14470#[gpui::test]
14471async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14472    init_test(cx, |_| {});
14473
14474    let language = Arc::new(
14475        Language::new(
14476            LanguageConfig {
14477                brackets: BracketPairConfig {
14478                    pairs: vec![
14479                        BracketPair {
14480                            start: "{".to_string(),
14481                            end: "}".to_string(),
14482                            close: true,
14483                            surround: true,
14484                            newline: true,
14485                        },
14486                        BracketPair {
14487                            start: "/* ".to_string(),
14488                            end: " */".to_string(),
14489                            close: true,
14490                            surround: true,
14491                            newline: true,
14492                        },
14493                    ],
14494                    ..Default::default()
14495                },
14496                ..Default::default()
14497            },
14498            Some(tree_sitter_rust::LANGUAGE.into()),
14499        )
14500        .with_indents_query("")
14501        .unwrap(),
14502    );
14503
14504    let text = concat!(
14505        "{   }\n",     //
14506        "  x\n",       //
14507        "  /*   */\n", //
14508        "x\n",         //
14509        "{{} }\n",     //
14510    );
14511
14512    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14513    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14514    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14515    editor
14516        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14517        .await;
14518
14519    editor.update_in(cx, |editor, window, cx| {
14520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14521            s.select_display_ranges([
14522                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14523                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14524                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14525            ])
14526        });
14527        editor.newline(&Newline, window, cx);
14528
14529        assert_eq!(
14530            editor.buffer().read(cx).read(cx).text(),
14531            concat!(
14532                "{ \n",    // Suppress rustfmt
14533                "\n",      //
14534                "}\n",     //
14535                "  x\n",   //
14536                "  /* \n", //
14537                "  \n",    //
14538                "  */\n",  //
14539                "x\n",     //
14540                "{{} \n",  //
14541                "}\n",     //
14542            )
14543        );
14544    });
14545}
14546
14547#[gpui::test]
14548fn test_highlighted_ranges(cx: &mut TestAppContext) {
14549    init_test(cx, |_| {});
14550
14551    let editor = cx.add_window(|window, cx| {
14552        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14553        build_editor(buffer.clone(), window, cx)
14554    });
14555
14556    _ = editor.update(cx, |editor, window, cx| {
14557        struct Type1;
14558        struct Type2;
14559
14560        let buffer = editor.buffer.read(cx).snapshot(cx);
14561
14562        let anchor_range =
14563            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14564
14565        editor.highlight_background::<Type1>(
14566            &[
14567                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14568                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14569                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14570                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14571            ],
14572            |_| Hsla::red(),
14573            cx,
14574        );
14575        editor.highlight_background::<Type2>(
14576            &[
14577                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14578                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14579                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14580                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14581            ],
14582            |_| Hsla::green(),
14583            cx,
14584        );
14585
14586        let snapshot = editor.snapshot(window, cx);
14587        let mut highlighted_ranges = editor.background_highlights_in_range(
14588            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14589            &snapshot,
14590            cx.theme(),
14591        );
14592        // Enforce a consistent ordering based on color without relying on the ordering of the
14593        // highlight's `TypeId` which is non-executor.
14594        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14595        assert_eq!(
14596            highlighted_ranges,
14597            &[
14598                (
14599                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14600                    Hsla::red(),
14601                ),
14602                (
14603                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14604                    Hsla::red(),
14605                ),
14606                (
14607                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14608                    Hsla::green(),
14609                ),
14610                (
14611                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14612                    Hsla::green(),
14613                ),
14614            ]
14615        );
14616        assert_eq!(
14617            editor.background_highlights_in_range(
14618                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14619                &snapshot,
14620                cx.theme(),
14621            ),
14622            &[(
14623                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14624                Hsla::red(),
14625            )]
14626        );
14627    });
14628}
14629
14630#[gpui::test]
14631async fn test_following(cx: &mut TestAppContext) {
14632    init_test(cx, |_| {});
14633
14634    let fs = FakeFs::new(cx.executor());
14635    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14636
14637    let buffer = project.update(cx, |project, cx| {
14638        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14639        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14640    });
14641    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14642    let follower = cx.update(|cx| {
14643        cx.open_window(
14644            WindowOptions {
14645                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14646                    gpui::Point::new(px(0.), px(0.)),
14647                    gpui::Point::new(px(10.), px(80.)),
14648                ))),
14649                ..Default::default()
14650            },
14651            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14652        )
14653        .unwrap()
14654    });
14655
14656    let is_still_following = Rc::new(RefCell::new(true));
14657    let follower_edit_event_count = Rc::new(RefCell::new(0));
14658    let pending_update = Rc::new(RefCell::new(None));
14659    let leader_entity = leader.root(cx).unwrap();
14660    let follower_entity = follower.root(cx).unwrap();
14661    _ = follower.update(cx, {
14662        let update = pending_update.clone();
14663        let is_still_following = is_still_following.clone();
14664        let follower_edit_event_count = follower_edit_event_count.clone();
14665        |_, window, cx| {
14666            cx.subscribe_in(
14667                &leader_entity,
14668                window,
14669                move |_, leader, event, window, cx| {
14670                    leader.read(cx).add_event_to_update_proto(
14671                        event,
14672                        &mut update.borrow_mut(),
14673                        window,
14674                        cx,
14675                    );
14676                },
14677            )
14678            .detach();
14679
14680            cx.subscribe_in(
14681                &follower_entity,
14682                window,
14683                move |_, _, event: &EditorEvent, _window, _cx| {
14684                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14685                        *is_still_following.borrow_mut() = false;
14686                    }
14687
14688                    if let EditorEvent::BufferEdited = event {
14689                        *follower_edit_event_count.borrow_mut() += 1;
14690                    }
14691                },
14692            )
14693            .detach();
14694        }
14695    });
14696
14697    // Update the selections only
14698    _ = leader.update(cx, |leader, window, cx| {
14699        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14700            s.select_ranges([1..1])
14701        });
14702    });
14703    follower
14704        .update(cx, |follower, window, cx| {
14705            follower.apply_update_proto(
14706                &project,
14707                pending_update.borrow_mut().take().unwrap(),
14708                window,
14709                cx,
14710            )
14711        })
14712        .unwrap()
14713        .await
14714        .unwrap();
14715    _ = follower.update(cx, |follower, _, cx| {
14716        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14717    });
14718    assert!(*is_still_following.borrow());
14719    assert_eq!(*follower_edit_event_count.borrow(), 0);
14720
14721    // Update the scroll position only
14722    _ = leader.update(cx, |leader, window, cx| {
14723        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14724    });
14725    follower
14726        .update(cx, |follower, window, cx| {
14727            follower.apply_update_proto(
14728                &project,
14729                pending_update.borrow_mut().take().unwrap(),
14730                window,
14731                cx,
14732            )
14733        })
14734        .unwrap()
14735        .await
14736        .unwrap();
14737    assert_eq!(
14738        follower
14739            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14740            .unwrap(),
14741        gpui::Point::new(1.5, 3.5)
14742    );
14743    assert!(*is_still_following.borrow());
14744    assert_eq!(*follower_edit_event_count.borrow(), 0);
14745
14746    // Update the selections and scroll position. The follower's scroll position is updated
14747    // via autoscroll, not via the leader's exact scroll position.
14748    _ = leader.update(cx, |leader, window, cx| {
14749        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14750            s.select_ranges([0..0])
14751        });
14752        leader.request_autoscroll(Autoscroll::newest(), cx);
14753        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14754    });
14755    follower
14756        .update(cx, |follower, window, cx| {
14757            follower.apply_update_proto(
14758                &project,
14759                pending_update.borrow_mut().take().unwrap(),
14760                window,
14761                cx,
14762            )
14763        })
14764        .unwrap()
14765        .await
14766        .unwrap();
14767    _ = follower.update(cx, |follower, _, cx| {
14768        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14769        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14770    });
14771    assert!(*is_still_following.borrow());
14772
14773    // Creating a pending selection that precedes another selection
14774    _ = leader.update(cx, |leader, window, cx| {
14775        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14776            s.select_ranges([1..1])
14777        });
14778        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14779    });
14780    follower
14781        .update(cx, |follower, window, cx| {
14782            follower.apply_update_proto(
14783                &project,
14784                pending_update.borrow_mut().take().unwrap(),
14785                window,
14786                cx,
14787            )
14788        })
14789        .unwrap()
14790        .await
14791        .unwrap();
14792    _ = follower.update(cx, |follower, _, cx| {
14793        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14794    });
14795    assert!(*is_still_following.borrow());
14796
14797    // Extend the pending selection so that it surrounds another selection
14798    _ = leader.update(cx, |leader, window, cx| {
14799        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14800    });
14801    follower
14802        .update(cx, |follower, window, cx| {
14803            follower.apply_update_proto(
14804                &project,
14805                pending_update.borrow_mut().take().unwrap(),
14806                window,
14807                cx,
14808            )
14809        })
14810        .unwrap()
14811        .await
14812        .unwrap();
14813    _ = follower.update(cx, |follower, _, cx| {
14814        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14815    });
14816
14817    // Scrolling locally breaks the follow
14818    _ = follower.update(cx, |follower, window, cx| {
14819        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14820        follower.set_scroll_anchor(
14821            ScrollAnchor {
14822                anchor: top_anchor,
14823                offset: gpui::Point::new(0.0, 0.5),
14824            },
14825            window,
14826            cx,
14827        );
14828    });
14829    assert!(!(*is_still_following.borrow()));
14830}
14831
14832#[gpui::test]
14833async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14834    init_test(cx, |_| {});
14835
14836    let fs = FakeFs::new(cx.executor());
14837    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14838    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14839    let pane = workspace
14840        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14841        .unwrap();
14842
14843    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14844
14845    let leader = pane.update_in(cx, |_, window, cx| {
14846        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14847        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14848    });
14849
14850    // Start following the editor when it has no excerpts.
14851    let mut state_message =
14852        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14853    let workspace_entity = workspace.root(cx).unwrap();
14854    let follower_1 = cx
14855        .update_window(*workspace.deref(), |_, window, cx| {
14856            Editor::from_state_proto(
14857                workspace_entity,
14858                ViewId {
14859                    creator: CollaboratorId::PeerId(PeerId::default()),
14860                    id: 0,
14861                },
14862                &mut state_message,
14863                window,
14864                cx,
14865            )
14866        })
14867        .unwrap()
14868        .unwrap()
14869        .await
14870        .unwrap();
14871
14872    let update_message = Rc::new(RefCell::new(None));
14873    follower_1.update_in(cx, {
14874        let update = update_message.clone();
14875        |_, window, cx| {
14876            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14877                leader.read(cx).add_event_to_update_proto(
14878                    event,
14879                    &mut update.borrow_mut(),
14880                    window,
14881                    cx,
14882                );
14883            })
14884            .detach();
14885        }
14886    });
14887
14888    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14889        (
14890            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14891            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14892        )
14893    });
14894
14895    // Insert some excerpts.
14896    leader.update(cx, |leader, cx| {
14897        leader.buffer.update(cx, |multibuffer, cx| {
14898            multibuffer.set_excerpts_for_path(
14899                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14900                buffer_1.clone(),
14901                vec![
14902                    Point::row_range(0..3),
14903                    Point::row_range(1..6),
14904                    Point::row_range(12..15),
14905                ],
14906                0,
14907                cx,
14908            );
14909            multibuffer.set_excerpts_for_path(
14910                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14911                buffer_2.clone(),
14912                vec![Point::row_range(0..6), Point::row_range(8..12)],
14913                0,
14914                cx,
14915            );
14916        });
14917    });
14918
14919    // Apply the update of adding the excerpts.
14920    follower_1
14921        .update_in(cx, |follower, window, cx| {
14922            follower.apply_update_proto(
14923                &project,
14924                update_message.borrow().clone().unwrap(),
14925                window,
14926                cx,
14927            )
14928        })
14929        .await
14930        .unwrap();
14931    assert_eq!(
14932        follower_1.update(cx, |editor, cx| editor.text(cx)),
14933        leader.update(cx, |editor, cx| editor.text(cx))
14934    );
14935    update_message.borrow_mut().take();
14936
14937    // Start following separately after it already has excerpts.
14938    let mut state_message =
14939        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14940    let workspace_entity = workspace.root(cx).unwrap();
14941    let follower_2 = cx
14942        .update_window(*workspace.deref(), |_, window, cx| {
14943            Editor::from_state_proto(
14944                workspace_entity,
14945                ViewId {
14946                    creator: CollaboratorId::PeerId(PeerId::default()),
14947                    id: 0,
14948                },
14949                &mut state_message,
14950                window,
14951                cx,
14952            )
14953        })
14954        .unwrap()
14955        .unwrap()
14956        .await
14957        .unwrap();
14958    assert_eq!(
14959        follower_2.update(cx, |editor, cx| editor.text(cx)),
14960        leader.update(cx, |editor, cx| editor.text(cx))
14961    );
14962
14963    // Remove some excerpts.
14964    leader.update(cx, |leader, cx| {
14965        leader.buffer.update(cx, |multibuffer, cx| {
14966            let excerpt_ids = multibuffer.excerpt_ids();
14967            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14968            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14969        });
14970    });
14971
14972    // Apply the update of removing the excerpts.
14973    follower_1
14974        .update_in(cx, |follower, window, cx| {
14975            follower.apply_update_proto(
14976                &project,
14977                update_message.borrow().clone().unwrap(),
14978                window,
14979                cx,
14980            )
14981        })
14982        .await
14983        .unwrap();
14984    follower_2
14985        .update_in(cx, |follower, window, cx| {
14986            follower.apply_update_proto(
14987                &project,
14988                update_message.borrow().clone().unwrap(),
14989                window,
14990                cx,
14991            )
14992        })
14993        .await
14994        .unwrap();
14995    update_message.borrow_mut().take();
14996    assert_eq!(
14997        follower_1.update(cx, |editor, cx| editor.text(cx)),
14998        leader.update(cx, |editor, cx| editor.text(cx))
14999    );
15000}
15001
15002#[gpui::test]
15003async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15004    init_test(cx, |_| {});
15005
15006    let mut cx = EditorTestContext::new(cx).await;
15007    let lsp_store =
15008        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
15009
15010    cx.set_state(indoc! {"
15011        ˇfn func(abc def: i32) -> u32 {
15012        }
15013    "});
15014
15015    cx.update(|_, cx| {
15016        lsp_store.update(cx, |lsp_store, cx| {
15017            lsp_store
15018                .update_diagnostics(
15019                    LanguageServerId(0),
15020                    lsp::PublishDiagnosticsParams {
15021                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15022                        version: None,
15023                        diagnostics: vec![
15024                            lsp::Diagnostic {
15025                                range: lsp::Range::new(
15026                                    lsp::Position::new(0, 11),
15027                                    lsp::Position::new(0, 12),
15028                                ),
15029                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15030                                ..Default::default()
15031                            },
15032                            lsp::Diagnostic {
15033                                range: lsp::Range::new(
15034                                    lsp::Position::new(0, 12),
15035                                    lsp::Position::new(0, 15),
15036                                ),
15037                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15038                                ..Default::default()
15039                            },
15040                            lsp::Diagnostic {
15041                                range: lsp::Range::new(
15042                                    lsp::Position::new(0, 25),
15043                                    lsp::Position::new(0, 28),
15044                                ),
15045                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15046                                ..Default::default()
15047                            },
15048                        ],
15049                    },
15050                    None,
15051                    DiagnosticSourceKind::Pushed,
15052                    &[],
15053                    cx,
15054                )
15055                .unwrap()
15056        });
15057    });
15058
15059    executor.run_until_parked();
15060
15061    cx.update_editor(|editor, window, cx| {
15062        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15063    });
15064
15065    cx.assert_editor_state(indoc! {"
15066        fn func(abc def: i32) -> ˇu32 {
15067        }
15068    "});
15069
15070    cx.update_editor(|editor, window, cx| {
15071        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15072    });
15073
15074    cx.assert_editor_state(indoc! {"
15075        fn func(abc ˇdef: i32) -> u32 {
15076        }
15077    "});
15078
15079    cx.update_editor(|editor, window, cx| {
15080        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15081    });
15082
15083    cx.assert_editor_state(indoc! {"
15084        fn func(abcˇ def: i32) -> u32 {
15085        }
15086    "});
15087
15088    cx.update_editor(|editor, window, cx| {
15089        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15090    });
15091
15092    cx.assert_editor_state(indoc! {"
15093        fn func(abc def: i32) -> ˇu32 {
15094        }
15095    "});
15096}
15097
15098#[gpui::test]
15099async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15100    init_test(cx, |_| {});
15101
15102    let mut cx = EditorTestContext::new(cx).await;
15103
15104    let diff_base = r#"
15105        use some::mod;
15106
15107        const A: u32 = 42;
15108
15109        fn main() {
15110            println!("hello");
15111
15112            println!("world");
15113        }
15114        "#
15115    .unindent();
15116
15117    // Edits are modified, removed, modified, added
15118    cx.set_state(
15119        &r#"
15120        use some::modified;
15121
15122        ˇ
15123        fn main() {
15124            println!("hello there");
15125
15126            println!("around the");
15127            println!("world");
15128        }
15129        "#
15130        .unindent(),
15131    );
15132
15133    cx.set_head_text(&diff_base);
15134    executor.run_until_parked();
15135
15136    cx.update_editor(|editor, window, cx| {
15137        //Wrap around the bottom of the buffer
15138        for _ in 0..3 {
15139            editor.go_to_next_hunk(&GoToHunk, window, cx);
15140        }
15141    });
15142
15143    cx.assert_editor_state(
15144        &r#"
15145        ˇuse some::modified;
15146
15147
15148        fn main() {
15149            println!("hello there");
15150
15151            println!("around the");
15152            println!("world");
15153        }
15154        "#
15155        .unindent(),
15156    );
15157
15158    cx.update_editor(|editor, window, cx| {
15159        //Wrap around the top of the buffer
15160        for _ in 0..2 {
15161            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15162        }
15163    });
15164
15165    cx.assert_editor_state(
15166        &r#"
15167        use some::modified;
15168
15169
15170        fn main() {
15171        ˇ    println!("hello there");
15172
15173            println!("around the");
15174            println!("world");
15175        }
15176        "#
15177        .unindent(),
15178    );
15179
15180    cx.update_editor(|editor, window, cx| {
15181        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15182    });
15183
15184    cx.assert_editor_state(
15185        &r#"
15186        use some::modified;
15187
15188        ˇ
15189        fn main() {
15190            println!("hello there");
15191
15192            println!("around the");
15193            println!("world");
15194        }
15195        "#
15196        .unindent(),
15197    );
15198
15199    cx.update_editor(|editor, window, cx| {
15200        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15201    });
15202
15203    cx.assert_editor_state(
15204        &r#"
15205        ˇuse some::modified;
15206
15207
15208        fn main() {
15209            println!("hello there");
15210
15211            println!("around the");
15212            println!("world");
15213        }
15214        "#
15215        .unindent(),
15216    );
15217
15218    cx.update_editor(|editor, window, cx| {
15219        for _ in 0..2 {
15220            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15221        }
15222    });
15223
15224    cx.assert_editor_state(
15225        &r#"
15226        use some::modified;
15227
15228
15229        fn main() {
15230        ˇ    println!("hello there");
15231
15232            println!("around the");
15233            println!("world");
15234        }
15235        "#
15236        .unindent(),
15237    );
15238
15239    cx.update_editor(|editor, window, cx| {
15240        editor.fold(&Fold, window, cx);
15241    });
15242
15243    cx.update_editor(|editor, window, cx| {
15244        editor.go_to_next_hunk(&GoToHunk, window, cx);
15245    });
15246
15247    cx.assert_editor_state(
15248        &r#"
15249        ˇuse some::modified;
15250
15251
15252        fn main() {
15253            println!("hello there");
15254
15255            println!("around the");
15256            println!("world");
15257        }
15258        "#
15259        .unindent(),
15260    );
15261}
15262
15263#[test]
15264fn test_split_words() {
15265    fn split(text: &str) -> Vec<&str> {
15266        split_words(text).collect()
15267    }
15268
15269    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15270    assert_eq!(split("hello_world"), &["hello_", "world"]);
15271    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15272    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15273    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15274    assert_eq!(split("helloworld"), &["helloworld"]);
15275
15276    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15277}
15278
15279#[gpui::test]
15280async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15281    init_test(cx, |_| {});
15282
15283    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15284    let mut assert = |before, after| {
15285        let _state_context = cx.set_state(before);
15286        cx.run_until_parked();
15287        cx.update_editor(|editor, window, cx| {
15288            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15289        });
15290        cx.run_until_parked();
15291        cx.assert_editor_state(after);
15292    };
15293
15294    // Outside bracket jumps to outside of matching bracket
15295    assert("console.logˇ(var);", "console.log(var)ˇ;");
15296    assert("console.log(var)ˇ;", "console.logˇ(var);");
15297
15298    // Inside bracket jumps to inside of matching bracket
15299    assert("console.log(ˇvar);", "console.log(varˇ);");
15300    assert("console.log(varˇ);", "console.log(ˇvar);");
15301
15302    // When outside a bracket and inside, favor jumping to the inside bracket
15303    assert(
15304        "console.log('foo', [1, 2, 3]ˇ);",
15305        "console.log(ˇ'foo', [1, 2, 3]);",
15306    );
15307    assert(
15308        "console.log(ˇ'foo', [1, 2, 3]);",
15309        "console.log('foo', [1, 2, 3]ˇ);",
15310    );
15311
15312    // Bias forward if two options are equally likely
15313    assert(
15314        "let result = curried_fun()ˇ();",
15315        "let result = curried_fun()()ˇ;",
15316    );
15317
15318    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15319    assert(
15320        indoc! {"
15321            function test() {
15322                console.log('test')ˇ
15323            }"},
15324        indoc! {"
15325            function test() {
15326                console.logˇ('test')
15327            }"},
15328    );
15329}
15330
15331#[gpui::test]
15332async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15333    init_test(cx, |_| {});
15334
15335    let fs = FakeFs::new(cx.executor());
15336    fs.insert_tree(
15337        path!("/a"),
15338        json!({
15339            "main.rs": "fn main() { let a = 5; }",
15340            "other.rs": "// Test file",
15341        }),
15342    )
15343    .await;
15344    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15345
15346    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15347    language_registry.add(Arc::new(Language::new(
15348        LanguageConfig {
15349            name: "Rust".into(),
15350            matcher: LanguageMatcher {
15351                path_suffixes: vec!["rs".to_string()],
15352                ..Default::default()
15353            },
15354            brackets: BracketPairConfig {
15355                pairs: vec![BracketPair {
15356                    start: "{".to_string(),
15357                    end: "}".to_string(),
15358                    close: true,
15359                    surround: true,
15360                    newline: true,
15361                }],
15362                disabled_scopes_by_bracket_ix: Vec::new(),
15363            },
15364            ..Default::default()
15365        },
15366        Some(tree_sitter_rust::LANGUAGE.into()),
15367    )));
15368    let mut fake_servers = language_registry.register_fake_lsp(
15369        "Rust",
15370        FakeLspAdapter {
15371            capabilities: lsp::ServerCapabilities {
15372                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15373                    first_trigger_character: "{".to_string(),
15374                    more_trigger_character: None,
15375                }),
15376                ..Default::default()
15377            },
15378            ..Default::default()
15379        },
15380    );
15381
15382    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15383
15384    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15385
15386    let worktree_id = workspace
15387        .update(cx, |workspace, _, cx| {
15388            workspace.project().update(cx, |project, cx| {
15389                project.worktrees(cx).next().unwrap().read(cx).id()
15390            })
15391        })
15392        .unwrap();
15393
15394    let buffer = project
15395        .update(cx, |project, cx| {
15396            project.open_local_buffer(path!("/a/main.rs"), cx)
15397        })
15398        .await
15399        .unwrap();
15400    let editor_handle = workspace
15401        .update(cx, |workspace, window, cx| {
15402            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15403        })
15404        .unwrap()
15405        .await
15406        .unwrap()
15407        .downcast::<Editor>()
15408        .unwrap();
15409
15410    cx.executor().start_waiting();
15411    let fake_server = fake_servers.next().await.unwrap();
15412
15413    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15414        |params, _| async move {
15415            assert_eq!(
15416                params.text_document_position.text_document.uri,
15417                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15418            );
15419            assert_eq!(
15420                params.text_document_position.position,
15421                lsp::Position::new(0, 21),
15422            );
15423
15424            Ok(Some(vec![lsp::TextEdit {
15425                new_text: "]".to_string(),
15426                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15427            }]))
15428        },
15429    );
15430
15431    editor_handle.update_in(cx, |editor, window, cx| {
15432        window.focus(&editor.focus_handle(cx));
15433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15434            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15435        });
15436        editor.handle_input("{", window, cx);
15437    });
15438
15439    cx.executor().run_until_parked();
15440
15441    buffer.update(cx, |buffer, _| {
15442        assert_eq!(
15443            buffer.text(),
15444            "fn main() { let a = {5}; }",
15445            "No extra braces from on type formatting should appear in the buffer"
15446        )
15447    });
15448}
15449
15450#[gpui::test(iterations = 20, seeds(31))]
15451async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15452    init_test(cx, |_| {});
15453
15454    let mut cx = EditorLspTestContext::new_rust(
15455        lsp::ServerCapabilities {
15456            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15457                first_trigger_character: ".".to_string(),
15458                more_trigger_character: None,
15459            }),
15460            ..Default::default()
15461        },
15462        cx,
15463    )
15464    .await;
15465
15466    cx.update_buffer(|buffer, _| {
15467        // This causes autoindent to be async.
15468        buffer.set_sync_parse_timeout(Duration::ZERO)
15469    });
15470
15471    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15472    cx.simulate_keystroke("\n");
15473    cx.run_until_parked();
15474
15475    let buffer_cloned =
15476        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15477    let mut request =
15478        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15479            let buffer_cloned = buffer_cloned.clone();
15480            async move {
15481                buffer_cloned.update(&mut cx, |buffer, _| {
15482                    assert_eq!(
15483                        buffer.text(),
15484                        "fn c() {\n    d()\n        .\n}\n",
15485                        "OnTypeFormatting should triggered after autoindent applied"
15486                    )
15487                })?;
15488
15489                Ok(Some(vec![]))
15490            }
15491        });
15492
15493    cx.simulate_keystroke(".");
15494    cx.run_until_parked();
15495
15496    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15497    assert!(request.next().await.is_some());
15498    request.close();
15499    assert!(request.next().await.is_none());
15500}
15501
15502#[gpui::test]
15503async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15504    init_test(cx, |_| {});
15505
15506    let fs = FakeFs::new(cx.executor());
15507    fs.insert_tree(
15508        path!("/a"),
15509        json!({
15510            "main.rs": "fn main() { let a = 5; }",
15511            "other.rs": "// Test file",
15512        }),
15513    )
15514    .await;
15515
15516    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15517
15518    let server_restarts = Arc::new(AtomicUsize::new(0));
15519    let closure_restarts = Arc::clone(&server_restarts);
15520    let language_server_name = "test language server";
15521    let language_name: LanguageName = "Rust".into();
15522
15523    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15524    language_registry.add(Arc::new(Language::new(
15525        LanguageConfig {
15526            name: language_name.clone(),
15527            matcher: LanguageMatcher {
15528                path_suffixes: vec!["rs".to_string()],
15529                ..Default::default()
15530            },
15531            ..Default::default()
15532        },
15533        Some(tree_sitter_rust::LANGUAGE.into()),
15534    )));
15535    let mut fake_servers = language_registry.register_fake_lsp(
15536        "Rust",
15537        FakeLspAdapter {
15538            name: language_server_name,
15539            initialization_options: Some(json!({
15540                "testOptionValue": true
15541            })),
15542            initializer: Some(Box::new(move |fake_server| {
15543                let task_restarts = Arc::clone(&closure_restarts);
15544                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15545                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15546                    futures::future::ready(Ok(()))
15547                });
15548            })),
15549            ..Default::default()
15550        },
15551    );
15552
15553    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15554    let _buffer = project
15555        .update(cx, |project, cx| {
15556            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15557        })
15558        .await
15559        .unwrap();
15560    let _fake_server = fake_servers.next().await.unwrap();
15561    update_test_language_settings(cx, |language_settings| {
15562        language_settings.languages.0.insert(
15563            language_name.clone(),
15564            LanguageSettingsContent {
15565                tab_size: NonZeroU32::new(8),
15566                ..Default::default()
15567            },
15568        );
15569    });
15570    cx.executor().run_until_parked();
15571    assert_eq!(
15572        server_restarts.load(atomic::Ordering::Acquire),
15573        0,
15574        "Should not restart LSP server on an unrelated change"
15575    );
15576
15577    update_test_project_settings(cx, |project_settings| {
15578        project_settings.lsp.insert(
15579            "Some other server name".into(),
15580            LspSettings {
15581                binary: None,
15582                settings: None,
15583                initialization_options: Some(json!({
15584                    "some other init value": false
15585                })),
15586                enable_lsp_tasks: false,
15587            },
15588        );
15589    });
15590    cx.executor().run_until_parked();
15591    assert_eq!(
15592        server_restarts.load(atomic::Ordering::Acquire),
15593        0,
15594        "Should not restart LSP server on an unrelated LSP settings change"
15595    );
15596
15597    update_test_project_settings(cx, |project_settings| {
15598        project_settings.lsp.insert(
15599            language_server_name.into(),
15600            LspSettings {
15601                binary: None,
15602                settings: None,
15603                initialization_options: Some(json!({
15604                    "anotherInitValue": false
15605                })),
15606                enable_lsp_tasks: false,
15607            },
15608        );
15609    });
15610    cx.executor().run_until_parked();
15611    assert_eq!(
15612        server_restarts.load(atomic::Ordering::Acquire),
15613        1,
15614        "Should restart LSP server on a related LSP settings change"
15615    );
15616
15617    update_test_project_settings(cx, |project_settings| {
15618        project_settings.lsp.insert(
15619            language_server_name.into(),
15620            LspSettings {
15621                binary: None,
15622                settings: None,
15623                initialization_options: Some(json!({
15624                    "anotherInitValue": false
15625                })),
15626                enable_lsp_tasks: false,
15627            },
15628        );
15629    });
15630    cx.executor().run_until_parked();
15631    assert_eq!(
15632        server_restarts.load(atomic::Ordering::Acquire),
15633        1,
15634        "Should not restart LSP server on a related LSP settings change that is the same"
15635    );
15636
15637    update_test_project_settings(cx, |project_settings| {
15638        project_settings.lsp.insert(
15639            language_server_name.into(),
15640            LspSettings {
15641                binary: None,
15642                settings: None,
15643                initialization_options: None,
15644                enable_lsp_tasks: false,
15645            },
15646        );
15647    });
15648    cx.executor().run_until_parked();
15649    assert_eq!(
15650        server_restarts.load(atomic::Ordering::Acquire),
15651        2,
15652        "Should restart LSP server on another related LSP settings change"
15653    );
15654}
15655
15656#[gpui::test]
15657async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15658    init_test(cx, |_| {});
15659
15660    let mut cx = EditorLspTestContext::new_rust(
15661        lsp::ServerCapabilities {
15662            completion_provider: Some(lsp::CompletionOptions {
15663                trigger_characters: Some(vec![".".to_string()]),
15664                resolve_provider: Some(true),
15665                ..Default::default()
15666            }),
15667            ..Default::default()
15668        },
15669        cx,
15670    )
15671    .await;
15672
15673    cx.set_state("fn main() { let a = 2ˇ; }");
15674    cx.simulate_keystroke(".");
15675    let completion_item = lsp::CompletionItem {
15676        label: "some".into(),
15677        kind: Some(lsp::CompletionItemKind::SNIPPET),
15678        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15679        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15680            kind: lsp::MarkupKind::Markdown,
15681            value: "```rust\nSome(2)\n```".to_string(),
15682        })),
15683        deprecated: Some(false),
15684        sort_text: Some("fffffff2".to_string()),
15685        filter_text: Some("some".to_string()),
15686        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15687        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15688            range: lsp::Range {
15689                start: lsp::Position {
15690                    line: 0,
15691                    character: 22,
15692                },
15693                end: lsp::Position {
15694                    line: 0,
15695                    character: 22,
15696                },
15697            },
15698            new_text: "Some(2)".to_string(),
15699        })),
15700        additional_text_edits: Some(vec![lsp::TextEdit {
15701            range: lsp::Range {
15702                start: lsp::Position {
15703                    line: 0,
15704                    character: 20,
15705                },
15706                end: lsp::Position {
15707                    line: 0,
15708                    character: 22,
15709                },
15710            },
15711            new_text: "".to_string(),
15712        }]),
15713        ..Default::default()
15714    };
15715
15716    let closure_completion_item = completion_item.clone();
15717    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15718        let task_completion_item = closure_completion_item.clone();
15719        async move {
15720            Ok(Some(lsp::CompletionResponse::Array(vec![
15721                task_completion_item,
15722            ])))
15723        }
15724    });
15725
15726    request.next().await;
15727
15728    cx.condition(|editor, _| editor.context_menu_visible())
15729        .await;
15730    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15731        editor
15732            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15733            .unwrap()
15734    });
15735    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15736
15737    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15738        let task_completion_item = completion_item.clone();
15739        async move { Ok(task_completion_item) }
15740    })
15741    .next()
15742    .await
15743    .unwrap();
15744    apply_additional_edits.await.unwrap();
15745    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15746}
15747
15748#[gpui::test]
15749async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15750    init_test(cx, |_| {});
15751
15752    let mut cx = EditorLspTestContext::new_rust(
15753        lsp::ServerCapabilities {
15754            completion_provider: Some(lsp::CompletionOptions {
15755                trigger_characters: Some(vec![".".to_string()]),
15756                resolve_provider: Some(true),
15757                ..Default::default()
15758            }),
15759            ..Default::default()
15760        },
15761        cx,
15762    )
15763    .await;
15764
15765    cx.set_state("fn main() { let a = 2ˇ; }");
15766    cx.simulate_keystroke(".");
15767
15768    let item1 = lsp::CompletionItem {
15769        label: "method id()".to_string(),
15770        filter_text: Some("id".to_string()),
15771        detail: None,
15772        documentation: None,
15773        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15774            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15775            new_text: ".id".to_string(),
15776        })),
15777        ..lsp::CompletionItem::default()
15778    };
15779
15780    let item2 = lsp::CompletionItem {
15781        label: "other".to_string(),
15782        filter_text: Some("other".to_string()),
15783        detail: None,
15784        documentation: None,
15785        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15786            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15787            new_text: ".other".to_string(),
15788        })),
15789        ..lsp::CompletionItem::default()
15790    };
15791
15792    let item1 = item1.clone();
15793    cx.set_request_handler::<lsp::request::Completion, _, _>({
15794        let item1 = item1.clone();
15795        move |_, _, _| {
15796            let item1 = item1.clone();
15797            let item2 = item2.clone();
15798            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15799        }
15800    })
15801    .next()
15802    .await;
15803
15804    cx.condition(|editor, _| editor.context_menu_visible())
15805        .await;
15806    cx.update_editor(|editor, _, _| {
15807        let context_menu = editor.context_menu.borrow_mut();
15808        let context_menu = context_menu
15809            .as_ref()
15810            .expect("Should have the context menu deployed");
15811        match context_menu {
15812            CodeContextMenu::Completions(completions_menu) => {
15813                let completions = completions_menu.completions.borrow_mut();
15814                assert_eq!(
15815                    completions
15816                        .iter()
15817                        .map(|completion| &completion.label.text)
15818                        .collect::<Vec<_>>(),
15819                    vec!["method id()", "other"]
15820                )
15821            }
15822            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15823        }
15824    });
15825
15826    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15827        let item1 = item1.clone();
15828        move |_, item_to_resolve, _| {
15829            let item1 = item1.clone();
15830            async move {
15831                if item1 == item_to_resolve {
15832                    Ok(lsp::CompletionItem {
15833                        label: "method id()".to_string(),
15834                        filter_text: Some("id".to_string()),
15835                        detail: Some("Now resolved!".to_string()),
15836                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15837                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15838                            range: lsp::Range::new(
15839                                lsp::Position::new(0, 22),
15840                                lsp::Position::new(0, 22),
15841                            ),
15842                            new_text: ".id".to_string(),
15843                        })),
15844                        ..lsp::CompletionItem::default()
15845                    })
15846                } else {
15847                    Ok(item_to_resolve)
15848                }
15849            }
15850        }
15851    })
15852    .next()
15853    .await
15854    .unwrap();
15855    cx.run_until_parked();
15856
15857    cx.update_editor(|editor, window, cx| {
15858        editor.context_menu_next(&Default::default(), window, cx);
15859    });
15860
15861    cx.update_editor(|editor, _, _| {
15862        let context_menu = editor.context_menu.borrow_mut();
15863        let context_menu = context_menu
15864            .as_ref()
15865            .expect("Should have the context menu deployed");
15866        match context_menu {
15867            CodeContextMenu::Completions(completions_menu) => {
15868                let completions = completions_menu.completions.borrow_mut();
15869                assert_eq!(
15870                    completions
15871                        .iter()
15872                        .map(|completion| &completion.label.text)
15873                        .collect::<Vec<_>>(),
15874                    vec!["method id() Now resolved!", "other"],
15875                    "Should update first completion label, but not second as the filter text did not match."
15876                );
15877            }
15878            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15879        }
15880    });
15881}
15882
15883#[gpui::test]
15884async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15885    init_test(cx, |_| {});
15886    let mut cx = EditorLspTestContext::new_rust(
15887        lsp::ServerCapabilities {
15888            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15889            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15890            completion_provider: Some(lsp::CompletionOptions {
15891                resolve_provider: Some(true),
15892                ..Default::default()
15893            }),
15894            ..Default::default()
15895        },
15896        cx,
15897    )
15898    .await;
15899    cx.set_state(indoc! {"
15900        struct TestStruct {
15901            field: i32
15902        }
15903
15904        fn mainˇ() {
15905            let unused_var = 42;
15906            let test_struct = TestStruct { field: 42 };
15907        }
15908    "});
15909    let symbol_range = cx.lsp_range(indoc! {"
15910        struct TestStruct {
15911            field: i32
15912        }
15913
15914        «fn main»() {
15915            let unused_var = 42;
15916            let test_struct = TestStruct { field: 42 };
15917        }
15918    "});
15919    let mut hover_requests =
15920        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15921            Ok(Some(lsp::Hover {
15922                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15923                    kind: lsp::MarkupKind::Markdown,
15924                    value: "Function documentation".to_string(),
15925                }),
15926                range: Some(symbol_range),
15927            }))
15928        });
15929
15930    // Case 1: Test that code action menu hide hover popover
15931    cx.dispatch_action(Hover);
15932    hover_requests.next().await;
15933    cx.condition(|editor, _| editor.hover_state.visible()).await;
15934    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15935        move |_, _, _| async move {
15936            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15937                lsp::CodeAction {
15938                    title: "Remove unused variable".to_string(),
15939                    kind: Some(CodeActionKind::QUICKFIX),
15940                    edit: Some(lsp::WorkspaceEdit {
15941                        changes: Some(
15942                            [(
15943                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15944                                vec![lsp::TextEdit {
15945                                    range: lsp::Range::new(
15946                                        lsp::Position::new(5, 4),
15947                                        lsp::Position::new(5, 27),
15948                                    ),
15949                                    new_text: "".to_string(),
15950                                }],
15951                            )]
15952                            .into_iter()
15953                            .collect(),
15954                        ),
15955                        ..Default::default()
15956                    }),
15957                    ..Default::default()
15958                },
15959            )]))
15960        },
15961    );
15962    cx.update_editor(|editor, window, cx| {
15963        editor.toggle_code_actions(
15964            &ToggleCodeActions {
15965                deployed_from: None,
15966                quick_launch: false,
15967            },
15968            window,
15969            cx,
15970        );
15971    });
15972    code_action_requests.next().await;
15973    cx.run_until_parked();
15974    cx.condition(|editor, _| editor.context_menu_visible())
15975        .await;
15976    cx.update_editor(|editor, _, _| {
15977        assert!(
15978            !editor.hover_state.visible(),
15979            "Hover popover should be hidden when code action menu is shown"
15980        );
15981        // Hide code actions
15982        editor.context_menu.take();
15983    });
15984
15985    // Case 2: Test that code completions hide hover popover
15986    cx.dispatch_action(Hover);
15987    hover_requests.next().await;
15988    cx.condition(|editor, _| editor.hover_state.visible()).await;
15989    let counter = Arc::new(AtomicUsize::new(0));
15990    let mut completion_requests =
15991        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15992            let counter = counter.clone();
15993            async move {
15994                counter.fetch_add(1, atomic::Ordering::Release);
15995                Ok(Some(lsp::CompletionResponse::Array(vec![
15996                    lsp::CompletionItem {
15997                        label: "main".into(),
15998                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15999                        detail: Some("() -> ()".to_string()),
16000                        ..Default::default()
16001                    },
16002                    lsp::CompletionItem {
16003                        label: "TestStruct".into(),
16004                        kind: Some(lsp::CompletionItemKind::STRUCT),
16005                        detail: Some("struct TestStruct".to_string()),
16006                        ..Default::default()
16007                    },
16008                ])))
16009            }
16010        });
16011    cx.update_editor(|editor, window, cx| {
16012        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16013    });
16014    completion_requests.next().await;
16015    cx.condition(|editor, _| editor.context_menu_visible())
16016        .await;
16017    cx.update_editor(|editor, _, _| {
16018        assert!(
16019            !editor.hover_state.visible(),
16020            "Hover popover should be hidden when completion menu is shown"
16021        );
16022    });
16023}
16024
16025#[gpui::test]
16026async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16027    init_test(cx, |_| {});
16028
16029    let mut cx = EditorLspTestContext::new_rust(
16030        lsp::ServerCapabilities {
16031            completion_provider: Some(lsp::CompletionOptions {
16032                trigger_characters: Some(vec![".".to_string()]),
16033                resolve_provider: Some(true),
16034                ..Default::default()
16035            }),
16036            ..Default::default()
16037        },
16038        cx,
16039    )
16040    .await;
16041
16042    cx.set_state("fn main() { let a = 2ˇ; }");
16043    cx.simulate_keystroke(".");
16044
16045    let unresolved_item_1 = lsp::CompletionItem {
16046        label: "id".to_string(),
16047        filter_text: Some("id".to_string()),
16048        detail: None,
16049        documentation: None,
16050        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16051            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16052            new_text: ".id".to_string(),
16053        })),
16054        ..lsp::CompletionItem::default()
16055    };
16056    let resolved_item_1 = lsp::CompletionItem {
16057        additional_text_edits: Some(vec![lsp::TextEdit {
16058            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16059            new_text: "!!".to_string(),
16060        }]),
16061        ..unresolved_item_1.clone()
16062    };
16063    let unresolved_item_2 = lsp::CompletionItem {
16064        label: "other".to_string(),
16065        filter_text: Some("other".to_string()),
16066        detail: None,
16067        documentation: None,
16068        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16069            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16070            new_text: ".other".to_string(),
16071        })),
16072        ..lsp::CompletionItem::default()
16073    };
16074    let resolved_item_2 = lsp::CompletionItem {
16075        additional_text_edits: Some(vec![lsp::TextEdit {
16076            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16077            new_text: "??".to_string(),
16078        }]),
16079        ..unresolved_item_2.clone()
16080    };
16081
16082    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16083    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16084    cx.lsp
16085        .server
16086        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16087            let unresolved_item_1 = unresolved_item_1.clone();
16088            let resolved_item_1 = resolved_item_1.clone();
16089            let unresolved_item_2 = unresolved_item_2.clone();
16090            let resolved_item_2 = resolved_item_2.clone();
16091            let resolve_requests_1 = resolve_requests_1.clone();
16092            let resolve_requests_2 = resolve_requests_2.clone();
16093            move |unresolved_request, _| {
16094                let unresolved_item_1 = unresolved_item_1.clone();
16095                let resolved_item_1 = resolved_item_1.clone();
16096                let unresolved_item_2 = unresolved_item_2.clone();
16097                let resolved_item_2 = resolved_item_2.clone();
16098                let resolve_requests_1 = resolve_requests_1.clone();
16099                let resolve_requests_2 = resolve_requests_2.clone();
16100                async move {
16101                    if unresolved_request == unresolved_item_1 {
16102                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16103                        Ok(resolved_item_1.clone())
16104                    } else if unresolved_request == unresolved_item_2 {
16105                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16106                        Ok(resolved_item_2.clone())
16107                    } else {
16108                        panic!("Unexpected completion item {unresolved_request:?}")
16109                    }
16110                }
16111            }
16112        })
16113        .detach();
16114
16115    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16116        let unresolved_item_1 = unresolved_item_1.clone();
16117        let unresolved_item_2 = unresolved_item_2.clone();
16118        async move {
16119            Ok(Some(lsp::CompletionResponse::Array(vec![
16120                unresolved_item_1,
16121                unresolved_item_2,
16122            ])))
16123        }
16124    })
16125    .next()
16126    .await;
16127
16128    cx.condition(|editor, _| editor.context_menu_visible())
16129        .await;
16130    cx.update_editor(|editor, _, _| {
16131        let context_menu = editor.context_menu.borrow_mut();
16132        let context_menu = context_menu
16133            .as_ref()
16134            .expect("Should have the context menu deployed");
16135        match context_menu {
16136            CodeContextMenu::Completions(completions_menu) => {
16137                let completions = completions_menu.completions.borrow_mut();
16138                assert_eq!(
16139                    completions
16140                        .iter()
16141                        .map(|completion| &completion.label.text)
16142                        .collect::<Vec<_>>(),
16143                    vec!["id", "other"]
16144                )
16145            }
16146            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16147        }
16148    });
16149    cx.run_until_parked();
16150
16151    cx.update_editor(|editor, window, cx| {
16152        editor.context_menu_next(&ContextMenuNext, window, cx);
16153    });
16154    cx.run_until_parked();
16155    cx.update_editor(|editor, window, cx| {
16156        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16157    });
16158    cx.run_until_parked();
16159    cx.update_editor(|editor, window, cx| {
16160        editor.context_menu_next(&ContextMenuNext, window, cx);
16161    });
16162    cx.run_until_parked();
16163    cx.update_editor(|editor, window, cx| {
16164        editor
16165            .compose_completion(&ComposeCompletion::default(), window, cx)
16166            .expect("No task returned")
16167    })
16168    .await
16169    .expect("Completion failed");
16170    cx.run_until_parked();
16171
16172    cx.update_editor(|editor, _, cx| {
16173        assert_eq!(
16174            resolve_requests_1.load(atomic::Ordering::Acquire),
16175            1,
16176            "Should always resolve once despite multiple selections"
16177        );
16178        assert_eq!(
16179            resolve_requests_2.load(atomic::Ordering::Acquire),
16180            1,
16181            "Should always resolve once after multiple selections and applying the completion"
16182        );
16183        assert_eq!(
16184            editor.text(cx),
16185            "fn main() { let a = ??.other; }",
16186            "Should use resolved data when applying the completion"
16187        );
16188    });
16189}
16190
16191#[gpui::test]
16192async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16193    init_test(cx, |_| {});
16194
16195    let item_0 = lsp::CompletionItem {
16196        label: "abs".into(),
16197        insert_text: Some("abs".into()),
16198        data: Some(json!({ "very": "special"})),
16199        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16200        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16201            lsp::InsertReplaceEdit {
16202                new_text: "abs".to_string(),
16203                insert: lsp::Range::default(),
16204                replace: lsp::Range::default(),
16205            },
16206        )),
16207        ..lsp::CompletionItem::default()
16208    };
16209    let items = iter::once(item_0.clone())
16210        .chain((11..51).map(|i| lsp::CompletionItem {
16211            label: format!("item_{}", i),
16212            insert_text: Some(format!("item_{}", i)),
16213            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16214            ..lsp::CompletionItem::default()
16215        }))
16216        .collect::<Vec<_>>();
16217
16218    let default_commit_characters = vec!["?".to_string()];
16219    let default_data = json!({ "default": "data"});
16220    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16221    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16222    let default_edit_range = lsp::Range {
16223        start: lsp::Position {
16224            line: 0,
16225            character: 5,
16226        },
16227        end: lsp::Position {
16228            line: 0,
16229            character: 5,
16230        },
16231    };
16232
16233    let mut cx = EditorLspTestContext::new_rust(
16234        lsp::ServerCapabilities {
16235            completion_provider: Some(lsp::CompletionOptions {
16236                trigger_characters: Some(vec![".".to_string()]),
16237                resolve_provider: Some(true),
16238                ..Default::default()
16239            }),
16240            ..Default::default()
16241        },
16242        cx,
16243    )
16244    .await;
16245
16246    cx.set_state("fn main() { let a = 2ˇ; }");
16247    cx.simulate_keystroke(".");
16248
16249    let completion_data = default_data.clone();
16250    let completion_characters = default_commit_characters.clone();
16251    let completion_items = items.clone();
16252    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16253        let default_data = completion_data.clone();
16254        let default_commit_characters = completion_characters.clone();
16255        let items = completion_items.clone();
16256        async move {
16257            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16258                items,
16259                item_defaults: Some(lsp::CompletionListItemDefaults {
16260                    data: Some(default_data.clone()),
16261                    commit_characters: Some(default_commit_characters.clone()),
16262                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16263                        default_edit_range,
16264                    )),
16265                    insert_text_format: Some(default_insert_text_format),
16266                    insert_text_mode: Some(default_insert_text_mode),
16267                }),
16268                ..lsp::CompletionList::default()
16269            })))
16270        }
16271    })
16272    .next()
16273    .await;
16274
16275    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16276    cx.lsp
16277        .server
16278        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16279            let closure_resolved_items = resolved_items.clone();
16280            move |item_to_resolve, _| {
16281                let closure_resolved_items = closure_resolved_items.clone();
16282                async move {
16283                    closure_resolved_items.lock().push(item_to_resolve.clone());
16284                    Ok(item_to_resolve)
16285                }
16286            }
16287        })
16288        .detach();
16289
16290    cx.condition(|editor, _| editor.context_menu_visible())
16291        .await;
16292    cx.run_until_parked();
16293    cx.update_editor(|editor, _, _| {
16294        let menu = editor.context_menu.borrow_mut();
16295        match menu.as_ref().expect("should have the completions menu") {
16296            CodeContextMenu::Completions(completions_menu) => {
16297                assert_eq!(
16298                    completions_menu
16299                        .entries
16300                        .borrow()
16301                        .iter()
16302                        .map(|mat| mat.string.clone())
16303                        .collect::<Vec<String>>(),
16304                    items
16305                        .iter()
16306                        .map(|completion| completion.label.clone())
16307                        .collect::<Vec<String>>()
16308                );
16309            }
16310            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16311        }
16312    });
16313    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16314    // with 4 from the end.
16315    assert_eq!(
16316        *resolved_items.lock(),
16317        [&items[0..16], &items[items.len() - 4..items.len()]]
16318            .concat()
16319            .iter()
16320            .cloned()
16321            .map(|mut item| {
16322                if item.data.is_none() {
16323                    item.data = Some(default_data.clone());
16324                }
16325                item
16326            })
16327            .collect::<Vec<lsp::CompletionItem>>(),
16328        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16329    );
16330    resolved_items.lock().clear();
16331
16332    cx.update_editor(|editor, window, cx| {
16333        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16334    });
16335    cx.run_until_parked();
16336    // Completions that have already been resolved are skipped.
16337    assert_eq!(
16338        *resolved_items.lock(),
16339        items[items.len() - 17..items.len() - 4]
16340            .iter()
16341            .cloned()
16342            .map(|mut item| {
16343                if item.data.is_none() {
16344                    item.data = Some(default_data.clone());
16345                }
16346                item
16347            })
16348            .collect::<Vec<lsp::CompletionItem>>()
16349    );
16350    resolved_items.lock().clear();
16351}
16352
16353#[gpui::test]
16354async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16355    init_test(cx, |_| {});
16356
16357    let mut cx = EditorLspTestContext::new(
16358        Language::new(
16359            LanguageConfig {
16360                matcher: LanguageMatcher {
16361                    path_suffixes: vec!["jsx".into()],
16362                    ..Default::default()
16363                },
16364                overrides: [(
16365                    "element".into(),
16366                    LanguageConfigOverride {
16367                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16368                        ..Default::default()
16369                    },
16370                )]
16371                .into_iter()
16372                .collect(),
16373                ..Default::default()
16374            },
16375            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16376        )
16377        .with_override_query("(jsx_self_closing_element) @element")
16378        .unwrap(),
16379        lsp::ServerCapabilities {
16380            completion_provider: Some(lsp::CompletionOptions {
16381                trigger_characters: Some(vec![":".to_string()]),
16382                ..Default::default()
16383            }),
16384            ..Default::default()
16385        },
16386        cx,
16387    )
16388    .await;
16389
16390    cx.lsp
16391        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16392            Ok(Some(lsp::CompletionResponse::Array(vec![
16393                lsp::CompletionItem {
16394                    label: "bg-blue".into(),
16395                    ..Default::default()
16396                },
16397                lsp::CompletionItem {
16398                    label: "bg-red".into(),
16399                    ..Default::default()
16400                },
16401                lsp::CompletionItem {
16402                    label: "bg-yellow".into(),
16403                    ..Default::default()
16404                },
16405            ])))
16406        });
16407
16408    cx.set_state(r#"<p class="bgˇ" />"#);
16409
16410    // Trigger completion when typing a dash, because the dash is an extra
16411    // word character in the 'element' scope, which contains the cursor.
16412    cx.simulate_keystroke("-");
16413    cx.executor().run_until_parked();
16414    cx.update_editor(|editor, _, _| {
16415        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16416        {
16417            assert_eq!(
16418                completion_menu_entries(&menu),
16419                &["bg-blue", "bg-red", "bg-yellow"]
16420            );
16421        } else {
16422            panic!("expected completion menu to be open");
16423        }
16424    });
16425
16426    cx.simulate_keystroke("l");
16427    cx.executor().run_until_parked();
16428    cx.update_editor(|editor, _, _| {
16429        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16430        {
16431            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16432        } else {
16433            panic!("expected completion menu to be open");
16434        }
16435    });
16436
16437    // When filtering completions, consider the character after the '-' to
16438    // be the start of a subword.
16439    cx.set_state(r#"<p class="yelˇ" />"#);
16440    cx.simulate_keystroke("l");
16441    cx.executor().run_until_parked();
16442    cx.update_editor(|editor, _, _| {
16443        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16444        {
16445            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16446        } else {
16447            panic!("expected completion menu to be open");
16448        }
16449    });
16450}
16451
16452fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16453    let entries = menu.entries.borrow();
16454    entries.iter().map(|mat| mat.string.clone()).collect()
16455}
16456
16457#[gpui::test]
16458async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16459    init_test(cx, |settings| {
16460        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16461            Formatter::Prettier,
16462        )))
16463    });
16464
16465    let fs = FakeFs::new(cx.executor());
16466    fs.insert_file(path!("/file.ts"), Default::default()).await;
16467
16468    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16469    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16470
16471    language_registry.add(Arc::new(Language::new(
16472        LanguageConfig {
16473            name: "TypeScript".into(),
16474            matcher: LanguageMatcher {
16475                path_suffixes: vec!["ts".to_string()],
16476                ..Default::default()
16477            },
16478            ..Default::default()
16479        },
16480        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16481    )));
16482    update_test_language_settings(cx, |settings| {
16483        settings.defaults.prettier = Some(PrettierSettings {
16484            allowed: true,
16485            ..PrettierSettings::default()
16486        });
16487    });
16488
16489    let test_plugin = "test_plugin";
16490    let _ = language_registry.register_fake_lsp(
16491        "TypeScript",
16492        FakeLspAdapter {
16493            prettier_plugins: vec![test_plugin],
16494            ..Default::default()
16495        },
16496    );
16497
16498    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16499    let buffer = project
16500        .update(cx, |project, cx| {
16501            project.open_local_buffer(path!("/file.ts"), cx)
16502        })
16503        .await
16504        .unwrap();
16505
16506    let buffer_text = "one\ntwo\nthree\n";
16507    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16508    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16509    editor.update_in(cx, |editor, window, cx| {
16510        editor.set_text(buffer_text, window, cx)
16511    });
16512
16513    editor
16514        .update_in(cx, |editor, window, cx| {
16515            editor.perform_format(
16516                project.clone(),
16517                FormatTrigger::Manual,
16518                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16519                window,
16520                cx,
16521            )
16522        })
16523        .unwrap()
16524        .await;
16525    assert_eq!(
16526        editor.update(cx, |editor, cx| editor.text(cx)),
16527        buffer_text.to_string() + prettier_format_suffix,
16528        "Test prettier formatting was not applied to the original buffer text",
16529    );
16530
16531    update_test_language_settings(cx, |settings| {
16532        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16533    });
16534    let format = editor.update_in(cx, |editor, window, cx| {
16535        editor.perform_format(
16536            project.clone(),
16537            FormatTrigger::Manual,
16538            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16539            window,
16540            cx,
16541        )
16542    });
16543    format.await.unwrap();
16544    assert_eq!(
16545        editor.update(cx, |editor, cx| editor.text(cx)),
16546        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16547        "Autoformatting (via test prettier) was not applied to the original buffer text",
16548    );
16549}
16550
16551#[gpui::test]
16552async fn test_addition_reverts(cx: &mut TestAppContext) {
16553    init_test(cx, |_| {});
16554    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16555    let base_text = indoc! {r#"
16556        struct Row;
16557        struct Row1;
16558        struct Row2;
16559
16560        struct Row4;
16561        struct Row5;
16562        struct Row6;
16563
16564        struct Row8;
16565        struct Row9;
16566        struct Row10;"#};
16567
16568    // When addition hunks are not adjacent to carets, no hunk revert is performed
16569    assert_hunk_revert(
16570        indoc! {r#"struct Row;
16571                   struct Row1;
16572                   struct Row1.1;
16573                   struct Row1.2;
16574                   struct Row2;ˇ
16575
16576                   struct Row4;
16577                   struct Row5;
16578                   struct Row6;
16579
16580                   struct Row8;
16581                   ˇstruct Row9;
16582                   struct Row9.1;
16583                   struct Row9.2;
16584                   struct Row9.3;
16585                   struct Row10;"#},
16586        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16587        indoc! {r#"struct Row;
16588                   struct Row1;
16589                   struct Row1.1;
16590                   struct Row1.2;
16591                   struct Row2;ˇ
16592
16593                   struct Row4;
16594                   struct Row5;
16595                   struct Row6;
16596
16597                   struct Row8;
16598                   ˇstruct Row9;
16599                   struct Row9.1;
16600                   struct Row9.2;
16601                   struct Row9.3;
16602                   struct Row10;"#},
16603        base_text,
16604        &mut cx,
16605    );
16606    // Same for selections
16607    assert_hunk_revert(
16608        indoc! {r#"struct Row;
16609                   struct Row1;
16610                   struct Row2;
16611                   struct Row2.1;
16612                   struct Row2.2;
16613                   «ˇ
16614                   struct Row4;
16615                   struct» Row5;
16616                   «struct Row6;
16617                   ˇ»
16618                   struct Row9.1;
16619                   struct Row9.2;
16620                   struct Row9.3;
16621                   struct Row8;
16622                   struct Row9;
16623                   struct Row10;"#},
16624        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16625        indoc! {r#"struct Row;
16626                   struct Row1;
16627                   struct Row2;
16628                   struct Row2.1;
16629                   struct Row2.2;
16630                   «ˇ
16631                   struct Row4;
16632                   struct» Row5;
16633                   «struct Row6;
16634                   ˇ»
16635                   struct Row9.1;
16636                   struct Row9.2;
16637                   struct Row9.3;
16638                   struct Row8;
16639                   struct Row9;
16640                   struct Row10;"#},
16641        base_text,
16642        &mut cx,
16643    );
16644
16645    // When carets and selections intersect the addition hunks, those are reverted.
16646    // Adjacent carets got merged.
16647    assert_hunk_revert(
16648        indoc! {r#"struct Row;
16649                   ˇ// something on the top
16650                   struct Row1;
16651                   struct Row2;
16652                   struct Roˇw3.1;
16653                   struct Row2.2;
16654                   struct Row2.3;ˇ
16655
16656                   struct Row4;
16657                   struct ˇRow5.1;
16658                   struct Row5.2;
16659                   struct «Rowˇ»5.3;
16660                   struct Row5;
16661                   struct Row6;
16662                   ˇ
16663                   struct Row9.1;
16664                   struct «Rowˇ»9.2;
16665                   struct «ˇRow»9.3;
16666                   struct Row8;
16667                   struct Row9;
16668                   «ˇ// something on bottom»
16669                   struct Row10;"#},
16670        vec![
16671            DiffHunkStatusKind::Added,
16672            DiffHunkStatusKind::Added,
16673            DiffHunkStatusKind::Added,
16674            DiffHunkStatusKind::Added,
16675            DiffHunkStatusKind::Added,
16676        ],
16677        indoc! {r#"struct Row;
16678                   ˇstruct Row1;
16679                   struct Row2;
16680                   ˇ
16681                   struct Row4;
16682                   ˇstruct Row5;
16683                   struct Row6;
16684                   ˇ
16685                   ˇstruct Row8;
16686                   struct Row9;
16687                   ˇstruct Row10;"#},
16688        base_text,
16689        &mut cx,
16690    );
16691}
16692
16693#[gpui::test]
16694async fn test_modification_reverts(cx: &mut TestAppContext) {
16695    init_test(cx, |_| {});
16696    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16697    let base_text = indoc! {r#"
16698        struct Row;
16699        struct Row1;
16700        struct Row2;
16701
16702        struct Row4;
16703        struct Row5;
16704        struct Row6;
16705
16706        struct Row8;
16707        struct Row9;
16708        struct Row10;"#};
16709
16710    // Modification hunks behave the same as the addition ones.
16711    assert_hunk_revert(
16712        indoc! {r#"struct Row;
16713                   struct Row1;
16714                   struct Row33;
16715                   ˇ
16716                   struct Row4;
16717                   struct Row5;
16718                   struct Row6;
16719                   ˇ
16720                   struct Row99;
16721                   struct Row9;
16722                   struct Row10;"#},
16723        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16724        indoc! {r#"struct Row;
16725                   struct Row1;
16726                   struct Row33;
16727                   ˇ
16728                   struct Row4;
16729                   struct Row5;
16730                   struct Row6;
16731                   ˇ
16732                   struct Row99;
16733                   struct Row9;
16734                   struct Row10;"#},
16735        base_text,
16736        &mut cx,
16737    );
16738    assert_hunk_revert(
16739        indoc! {r#"struct Row;
16740                   struct Row1;
16741                   struct Row33;
16742                   «ˇ
16743                   struct Row4;
16744                   struct» Row5;
16745                   «struct Row6;
16746                   ˇ»
16747                   struct Row99;
16748                   struct Row9;
16749                   struct Row10;"#},
16750        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16751        indoc! {r#"struct Row;
16752                   struct Row1;
16753                   struct Row33;
16754                   «ˇ
16755                   struct Row4;
16756                   struct» Row5;
16757                   «struct Row6;
16758                   ˇ»
16759                   struct Row99;
16760                   struct Row9;
16761                   struct Row10;"#},
16762        base_text,
16763        &mut cx,
16764    );
16765
16766    assert_hunk_revert(
16767        indoc! {r#"ˇstruct Row1.1;
16768                   struct Row1;
16769                   «ˇstr»uct Row22;
16770
16771                   struct ˇRow44;
16772                   struct Row5;
16773                   struct «Rˇ»ow66;ˇ
16774
16775                   «struˇ»ct Row88;
16776                   struct Row9;
16777                   struct Row1011;ˇ"#},
16778        vec![
16779            DiffHunkStatusKind::Modified,
16780            DiffHunkStatusKind::Modified,
16781            DiffHunkStatusKind::Modified,
16782            DiffHunkStatusKind::Modified,
16783            DiffHunkStatusKind::Modified,
16784            DiffHunkStatusKind::Modified,
16785        ],
16786        indoc! {r#"struct Row;
16787                   ˇstruct Row1;
16788                   struct Row2;
16789                   ˇ
16790                   struct Row4;
16791                   ˇstruct Row5;
16792                   struct Row6;
16793                   ˇ
16794                   struct Row8;
16795                   ˇstruct Row9;
16796                   struct Row10;ˇ"#},
16797        base_text,
16798        &mut cx,
16799    );
16800}
16801
16802#[gpui::test]
16803async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16804    init_test(cx, |_| {});
16805    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16806    let base_text = indoc! {r#"
16807        one
16808
16809        two
16810        three
16811        "#};
16812
16813    cx.set_head_text(base_text);
16814    cx.set_state("\nˇ\n");
16815    cx.executor().run_until_parked();
16816    cx.update_editor(|editor, _window, cx| {
16817        editor.expand_selected_diff_hunks(cx);
16818    });
16819    cx.executor().run_until_parked();
16820    cx.update_editor(|editor, window, cx| {
16821        editor.backspace(&Default::default(), window, cx);
16822    });
16823    cx.run_until_parked();
16824    cx.assert_state_with_diff(
16825        indoc! {r#"
16826
16827        - two
16828        - threeˇ
16829        +
16830        "#}
16831        .to_string(),
16832    );
16833}
16834
16835#[gpui::test]
16836async fn test_deletion_reverts(cx: &mut TestAppContext) {
16837    init_test(cx, |_| {});
16838    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16839    let base_text = indoc! {r#"struct Row;
16840struct Row1;
16841struct Row2;
16842
16843struct Row4;
16844struct Row5;
16845struct Row6;
16846
16847struct Row8;
16848struct Row9;
16849struct Row10;"#};
16850
16851    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16852    assert_hunk_revert(
16853        indoc! {r#"struct Row;
16854                   struct Row2;
16855
16856                   ˇstruct Row4;
16857                   struct Row5;
16858                   struct Row6;
16859                   ˇ
16860                   struct Row8;
16861                   struct Row10;"#},
16862        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16863        indoc! {r#"struct Row;
16864                   struct Row2;
16865
16866                   ˇstruct Row4;
16867                   struct Row5;
16868                   struct Row6;
16869                   ˇ
16870                   struct Row8;
16871                   struct Row10;"#},
16872        base_text,
16873        &mut cx,
16874    );
16875    assert_hunk_revert(
16876        indoc! {r#"struct Row;
16877                   struct Row2;
16878
16879                   «ˇstruct Row4;
16880                   struct» Row5;
16881                   «struct Row6;
16882                   ˇ»
16883                   struct Row8;
16884                   struct Row10;"#},
16885        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16886        indoc! {r#"struct Row;
16887                   struct Row2;
16888
16889                   «ˇstruct Row4;
16890                   struct» Row5;
16891                   «struct Row6;
16892                   ˇ»
16893                   struct Row8;
16894                   struct Row10;"#},
16895        base_text,
16896        &mut cx,
16897    );
16898
16899    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16900    assert_hunk_revert(
16901        indoc! {r#"struct Row;
16902                   ˇstruct Row2;
16903
16904                   struct Row4;
16905                   struct Row5;
16906                   struct Row6;
16907
16908                   struct Row8;ˇ
16909                   struct Row10;"#},
16910        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16911        indoc! {r#"struct Row;
16912                   struct Row1;
16913                   ˇstruct Row2;
16914
16915                   struct Row4;
16916                   struct Row5;
16917                   struct Row6;
16918
16919                   struct Row8;ˇ
16920                   struct Row9;
16921                   struct Row10;"#},
16922        base_text,
16923        &mut cx,
16924    );
16925    assert_hunk_revert(
16926        indoc! {r#"struct Row;
16927                   struct Row2«ˇ;
16928                   struct Row4;
16929                   struct» Row5;
16930                   «struct Row6;
16931
16932                   struct Row8;ˇ»
16933                   struct Row10;"#},
16934        vec![
16935            DiffHunkStatusKind::Deleted,
16936            DiffHunkStatusKind::Deleted,
16937            DiffHunkStatusKind::Deleted,
16938        ],
16939        indoc! {r#"struct Row;
16940                   struct Row1;
16941                   struct Row2«ˇ;
16942
16943                   struct Row4;
16944                   struct» Row5;
16945                   «struct Row6;
16946
16947                   struct Row8;ˇ»
16948                   struct Row9;
16949                   struct Row10;"#},
16950        base_text,
16951        &mut cx,
16952    );
16953}
16954
16955#[gpui::test]
16956async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16957    init_test(cx, |_| {});
16958
16959    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16960    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16961    let base_text_3 =
16962        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16963
16964    let text_1 = edit_first_char_of_every_line(base_text_1);
16965    let text_2 = edit_first_char_of_every_line(base_text_2);
16966    let text_3 = edit_first_char_of_every_line(base_text_3);
16967
16968    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16969    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16970    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16971
16972    let multibuffer = cx.new(|cx| {
16973        let mut multibuffer = MultiBuffer::new(ReadWrite);
16974        multibuffer.push_excerpts(
16975            buffer_1.clone(),
16976            [
16977                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16978                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16979                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16980            ],
16981            cx,
16982        );
16983        multibuffer.push_excerpts(
16984            buffer_2.clone(),
16985            [
16986                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16987                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16988                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16989            ],
16990            cx,
16991        );
16992        multibuffer.push_excerpts(
16993            buffer_3.clone(),
16994            [
16995                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16996                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16997                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16998            ],
16999            cx,
17000        );
17001        multibuffer
17002    });
17003
17004    let fs = FakeFs::new(cx.executor());
17005    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17006    let (editor, cx) = cx
17007        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17008    editor.update_in(cx, |editor, _window, cx| {
17009        for (buffer, diff_base) in [
17010            (buffer_1.clone(), base_text_1),
17011            (buffer_2.clone(), base_text_2),
17012            (buffer_3.clone(), base_text_3),
17013        ] {
17014            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17015            editor
17016                .buffer
17017                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17018        }
17019    });
17020    cx.executor().run_until_parked();
17021
17022    editor.update_in(cx, |editor, window, cx| {
17023        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}");
17024        editor.select_all(&SelectAll, window, cx);
17025        editor.git_restore(&Default::default(), window, cx);
17026    });
17027    cx.executor().run_until_parked();
17028
17029    // When all ranges are selected, all buffer hunks are reverted.
17030    editor.update(cx, |editor, cx| {
17031        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");
17032    });
17033    buffer_1.update(cx, |buffer, _| {
17034        assert_eq!(buffer.text(), base_text_1);
17035    });
17036    buffer_2.update(cx, |buffer, _| {
17037        assert_eq!(buffer.text(), base_text_2);
17038    });
17039    buffer_3.update(cx, |buffer, _| {
17040        assert_eq!(buffer.text(), base_text_3);
17041    });
17042
17043    editor.update_in(cx, |editor, window, cx| {
17044        editor.undo(&Default::default(), window, cx);
17045    });
17046
17047    editor.update_in(cx, |editor, window, cx| {
17048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17049            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17050        });
17051        editor.git_restore(&Default::default(), window, cx);
17052    });
17053
17054    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17055    // but not affect buffer_2 and its related excerpts.
17056    editor.update(cx, |editor, cx| {
17057        assert_eq!(
17058            editor.text(cx),
17059            "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}"
17060        );
17061    });
17062    buffer_1.update(cx, |buffer, _| {
17063        assert_eq!(buffer.text(), base_text_1);
17064    });
17065    buffer_2.update(cx, |buffer, _| {
17066        assert_eq!(
17067            buffer.text(),
17068            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17069        );
17070    });
17071    buffer_3.update(cx, |buffer, _| {
17072        assert_eq!(
17073            buffer.text(),
17074            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17075        );
17076    });
17077
17078    fn edit_first_char_of_every_line(text: &str) -> String {
17079        text.split('\n')
17080            .map(|line| format!("X{}", &line[1..]))
17081            .collect::<Vec<_>>()
17082            .join("\n")
17083    }
17084}
17085
17086#[gpui::test]
17087async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17088    init_test(cx, |_| {});
17089
17090    let cols = 4;
17091    let rows = 10;
17092    let sample_text_1 = sample_text(rows, cols, 'a');
17093    assert_eq!(
17094        sample_text_1,
17095        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17096    );
17097    let sample_text_2 = sample_text(rows, cols, 'l');
17098    assert_eq!(
17099        sample_text_2,
17100        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17101    );
17102    let sample_text_3 = sample_text(rows, cols, 'v');
17103    assert_eq!(
17104        sample_text_3,
17105        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17106    );
17107
17108    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17109    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17110    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17111
17112    let multi_buffer = cx.new(|cx| {
17113        let mut multibuffer = MultiBuffer::new(ReadWrite);
17114        multibuffer.push_excerpts(
17115            buffer_1.clone(),
17116            [
17117                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17118                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17119                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17120            ],
17121            cx,
17122        );
17123        multibuffer.push_excerpts(
17124            buffer_2.clone(),
17125            [
17126                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17127                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17128                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17129            ],
17130            cx,
17131        );
17132        multibuffer.push_excerpts(
17133            buffer_3.clone(),
17134            [
17135                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17136                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17137                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17138            ],
17139            cx,
17140        );
17141        multibuffer
17142    });
17143
17144    let fs = FakeFs::new(cx.executor());
17145    fs.insert_tree(
17146        "/a",
17147        json!({
17148            "main.rs": sample_text_1,
17149            "other.rs": sample_text_2,
17150            "lib.rs": sample_text_3,
17151        }),
17152    )
17153    .await;
17154    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17155    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17156    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17157    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17158        Editor::new(
17159            EditorMode::full(),
17160            multi_buffer,
17161            Some(project.clone()),
17162            window,
17163            cx,
17164        )
17165    });
17166    let multibuffer_item_id = workspace
17167        .update(cx, |workspace, window, cx| {
17168            assert!(
17169                workspace.active_item(cx).is_none(),
17170                "active item should be None before the first item is added"
17171            );
17172            workspace.add_item_to_active_pane(
17173                Box::new(multi_buffer_editor.clone()),
17174                None,
17175                true,
17176                window,
17177                cx,
17178            );
17179            let active_item = workspace
17180                .active_item(cx)
17181                .expect("should have an active item after adding the multi buffer");
17182            assert!(
17183                !active_item.is_singleton(cx),
17184                "A multi buffer was expected to active after adding"
17185            );
17186            active_item.item_id()
17187        })
17188        .unwrap();
17189    cx.executor().run_until_parked();
17190
17191    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17192        editor.change_selections(
17193            SelectionEffects::scroll(Autoscroll::Next),
17194            window,
17195            cx,
17196            |s| s.select_ranges(Some(1..2)),
17197        );
17198        editor.open_excerpts(&OpenExcerpts, window, cx);
17199    });
17200    cx.executor().run_until_parked();
17201    let first_item_id = workspace
17202        .update(cx, |workspace, window, cx| {
17203            let active_item = workspace
17204                .active_item(cx)
17205                .expect("should have an active item after navigating into the 1st buffer");
17206            let first_item_id = active_item.item_id();
17207            assert_ne!(
17208                first_item_id, multibuffer_item_id,
17209                "Should navigate into the 1st buffer and activate it"
17210            );
17211            assert!(
17212                active_item.is_singleton(cx),
17213                "New active item should be a singleton buffer"
17214            );
17215            assert_eq!(
17216                active_item
17217                    .act_as::<Editor>(cx)
17218                    .expect("should have navigated into an editor for the 1st buffer")
17219                    .read(cx)
17220                    .text(cx),
17221                sample_text_1
17222            );
17223
17224            workspace
17225                .go_back(workspace.active_pane().downgrade(), window, cx)
17226                .detach_and_log_err(cx);
17227
17228            first_item_id
17229        })
17230        .unwrap();
17231    cx.executor().run_until_parked();
17232    workspace
17233        .update(cx, |workspace, _, cx| {
17234            let active_item = workspace
17235                .active_item(cx)
17236                .expect("should have an active item after navigating back");
17237            assert_eq!(
17238                active_item.item_id(),
17239                multibuffer_item_id,
17240                "Should navigate back to the multi buffer"
17241            );
17242            assert!(!active_item.is_singleton(cx));
17243        })
17244        .unwrap();
17245
17246    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17247        editor.change_selections(
17248            SelectionEffects::scroll(Autoscroll::Next),
17249            window,
17250            cx,
17251            |s| s.select_ranges(Some(39..40)),
17252        );
17253        editor.open_excerpts(&OpenExcerpts, window, cx);
17254    });
17255    cx.executor().run_until_parked();
17256    let second_item_id = workspace
17257        .update(cx, |workspace, window, cx| {
17258            let active_item = workspace
17259                .active_item(cx)
17260                .expect("should have an active item after navigating into the 2nd buffer");
17261            let second_item_id = active_item.item_id();
17262            assert_ne!(
17263                second_item_id, multibuffer_item_id,
17264                "Should navigate away from the multibuffer"
17265            );
17266            assert_ne!(
17267                second_item_id, first_item_id,
17268                "Should navigate into the 2nd buffer and activate it"
17269            );
17270            assert!(
17271                active_item.is_singleton(cx),
17272                "New active item should be a singleton buffer"
17273            );
17274            assert_eq!(
17275                active_item
17276                    .act_as::<Editor>(cx)
17277                    .expect("should have navigated into an editor")
17278                    .read(cx)
17279                    .text(cx),
17280                sample_text_2
17281            );
17282
17283            workspace
17284                .go_back(workspace.active_pane().downgrade(), window, cx)
17285                .detach_and_log_err(cx);
17286
17287            second_item_id
17288        })
17289        .unwrap();
17290    cx.executor().run_until_parked();
17291    workspace
17292        .update(cx, |workspace, _, cx| {
17293            let active_item = workspace
17294                .active_item(cx)
17295                .expect("should have an active item after navigating back from the 2nd buffer");
17296            assert_eq!(
17297                active_item.item_id(),
17298                multibuffer_item_id,
17299                "Should navigate back from the 2nd buffer to the multi buffer"
17300            );
17301            assert!(!active_item.is_singleton(cx));
17302        })
17303        .unwrap();
17304
17305    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17306        editor.change_selections(
17307            SelectionEffects::scroll(Autoscroll::Next),
17308            window,
17309            cx,
17310            |s| s.select_ranges(Some(70..70)),
17311        );
17312        editor.open_excerpts(&OpenExcerpts, window, cx);
17313    });
17314    cx.executor().run_until_parked();
17315    workspace
17316        .update(cx, |workspace, window, cx| {
17317            let active_item = workspace
17318                .active_item(cx)
17319                .expect("should have an active item after navigating into the 3rd buffer");
17320            let third_item_id = active_item.item_id();
17321            assert_ne!(
17322                third_item_id, multibuffer_item_id,
17323                "Should navigate into the 3rd buffer and activate it"
17324            );
17325            assert_ne!(third_item_id, first_item_id);
17326            assert_ne!(third_item_id, second_item_id);
17327            assert!(
17328                active_item.is_singleton(cx),
17329                "New active item should be a singleton buffer"
17330            );
17331            assert_eq!(
17332                active_item
17333                    .act_as::<Editor>(cx)
17334                    .expect("should have navigated into an editor")
17335                    .read(cx)
17336                    .text(cx),
17337                sample_text_3
17338            );
17339
17340            workspace
17341                .go_back(workspace.active_pane().downgrade(), window, cx)
17342                .detach_and_log_err(cx);
17343        })
17344        .unwrap();
17345    cx.executor().run_until_parked();
17346    workspace
17347        .update(cx, |workspace, _, cx| {
17348            let active_item = workspace
17349                .active_item(cx)
17350                .expect("should have an active item after navigating back from the 3rd buffer");
17351            assert_eq!(
17352                active_item.item_id(),
17353                multibuffer_item_id,
17354                "Should navigate back from the 3rd buffer to the multi buffer"
17355            );
17356            assert!(!active_item.is_singleton(cx));
17357        })
17358        .unwrap();
17359}
17360
17361#[gpui::test]
17362async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17363    init_test(cx, |_| {});
17364
17365    let mut cx = EditorTestContext::new(cx).await;
17366
17367    let diff_base = r#"
17368        use some::mod;
17369
17370        const A: u32 = 42;
17371
17372        fn main() {
17373            println!("hello");
17374
17375            println!("world");
17376        }
17377        "#
17378    .unindent();
17379
17380    cx.set_state(
17381        &r#"
17382        use some::modified;
17383
17384        ˇ
17385        fn main() {
17386            println!("hello there");
17387
17388            println!("around the");
17389            println!("world");
17390        }
17391        "#
17392        .unindent(),
17393    );
17394
17395    cx.set_head_text(&diff_base);
17396    executor.run_until_parked();
17397
17398    cx.update_editor(|editor, window, cx| {
17399        editor.go_to_next_hunk(&GoToHunk, window, cx);
17400        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17401    });
17402    executor.run_until_parked();
17403    cx.assert_state_with_diff(
17404        r#"
17405          use some::modified;
17406
17407
17408          fn main() {
17409        -     println!("hello");
17410        + ˇ    println!("hello there");
17411
17412              println!("around the");
17413              println!("world");
17414          }
17415        "#
17416        .unindent(),
17417    );
17418
17419    cx.update_editor(|editor, window, cx| {
17420        for _ in 0..2 {
17421            editor.go_to_next_hunk(&GoToHunk, window, cx);
17422            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17423        }
17424    });
17425    executor.run_until_parked();
17426    cx.assert_state_with_diff(
17427        r#"
17428        - use some::mod;
17429        + ˇuse some::modified;
17430
17431
17432          fn main() {
17433        -     println!("hello");
17434        +     println!("hello there");
17435
17436        +     println!("around the");
17437              println!("world");
17438          }
17439        "#
17440        .unindent(),
17441    );
17442
17443    cx.update_editor(|editor, window, cx| {
17444        editor.go_to_next_hunk(&GoToHunk, window, cx);
17445        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17446    });
17447    executor.run_until_parked();
17448    cx.assert_state_with_diff(
17449        r#"
17450        - use some::mod;
17451        + use some::modified;
17452
17453        - const A: u32 = 42;
17454          ˇ
17455          fn main() {
17456        -     println!("hello");
17457        +     println!("hello there");
17458
17459        +     println!("around the");
17460              println!("world");
17461          }
17462        "#
17463        .unindent(),
17464    );
17465
17466    cx.update_editor(|editor, window, cx| {
17467        editor.cancel(&Cancel, window, cx);
17468    });
17469
17470    cx.assert_state_with_diff(
17471        r#"
17472          use some::modified;
17473
17474          ˇ
17475          fn main() {
17476              println!("hello there");
17477
17478              println!("around the");
17479              println!("world");
17480          }
17481        "#
17482        .unindent(),
17483    );
17484}
17485
17486#[gpui::test]
17487async fn test_diff_base_change_with_expanded_diff_hunks(
17488    executor: BackgroundExecutor,
17489    cx: &mut TestAppContext,
17490) {
17491    init_test(cx, |_| {});
17492
17493    let mut cx = EditorTestContext::new(cx).await;
17494
17495    let diff_base = r#"
17496        use some::mod1;
17497        use some::mod2;
17498
17499        const A: u32 = 42;
17500        const B: u32 = 42;
17501        const C: u32 = 42;
17502
17503        fn main() {
17504            println!("hello");
17505
17506            println!("world");
17507        }
17508        "#
17509    .unindent();
17510
17511    cx.set_state(
17512        &r#"
17513        use some::mod2;
17514
17515        const A: u32 = 42;
17516        const C: u32 = 42;
17517
17518        fn main(ˇ) {
17519            //println!("hello");
17520
17521            println!("world");
17522            //
17523            //
17524        }
17525        "#
17526        .unindent(),
17527    );
17528
17529    cx.set_head_text(&diff_base);
17530    executor.run_until_parked();
17531
17532    cx.update_editor(|editor, window, cx| {
17533        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17534    });
17535    executor.run_until_parked();
17536    cx.assert_state_with_diff(
17537        r#"
17538        - use some::mod1;
17539          use some::mod2;
17540
17541          const A: u32 = 42;
17542        - const B: u32 = 42;
17543          const C: u32 = 42;
17544
17545          fn main(ˇ) {
17546        -     println!("hello");
17547        +     //println!("hello");
17548
17549              println!("world");
17550        +     //
17551        +     //
17552          }
17553        "#
17554        .unindent(),
17555    );
17556
17557    cx.set_head_text("new diff base!");
17558    executor.run_until_parked();
17559    cx.assert_state_with_diff(
17560        r#"
17561        - new diff base!
17562        + use some::mod2;
17563        +
17564        + const A: u32 = 42;
17565        + const C: u32 = 42;
17566        +
17567        + fn main(ˇ) {
17568        +     //println!("hello");
17569        +
17570        +     println!("world");
17571        +     //
17572        +     //
17573        + }
17574        "#
17575        .unindent(),
17576    );
17577}
17578
17579#[gpui::test]
17580async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17581    init_test(cx, |_| {});
17582
17583    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17584    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17585    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17586    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17587    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17588    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17589
17590    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17591    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17592    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17593
17594    let multi_buffer = cx.new(|cx| {
17595        let mut multibuffer = MultiBuffer::new(ReadWrite);
17596        multibuffer.push_excerpts(
17597            buffer_1.clone(),
17598            [
17599                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17600                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17601                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17602            ],
17603            cx,
17604        );
17605        multibuffer.push_excerpts(
17606            buffer_2.clone(),
17607            [
17608                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17609                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17610                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17611            ],
17612            cx,
17613        );
17614        multibuffer.push_excerpts(
17615            buffer_3.clone(),
17616            [
17617                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17618                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17619                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17620            ],
17621            cx,
17622        );
17623        multibuffer
17624    });
17625
17626    let editor =
17627        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17628    editor
17629        .update(cx, |editor, _window, cx| {
17630            for (buffer, diff_base) in [
17631                (buffer_1.clone(), file_1_old),
17632                (buffer_2.clone(), file_2_old),
17633                (buffer_3.clone(), file_3_old),
17634            ] {
17635                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17636                editor
17637                    .buffer
17638                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17639            }
17640        })
17641        .unwrap();
17642
17643    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17644    cx.run_until_parked();
17645
17646    cx.assert_editor_state(
17647        &"
17648            ˇaaa
17649            ccc
17650            ddd
17651
17652            ggg
17653            hhh
17654
17655
17656            lll
17657            mmm
17658            NNN
17659
17660            qqq
17661            rrr
17662
17663            uuu
17664            111
17665            222
17666            333
17667
17668            666
17669            777
17670
17671            000
17672            !!!"
17673        .unindent(),
17674    );
17675
17676    cx.update_editor(|editor, window, cx| {
17677        editor.select_all(&SelectAll, window, cx);
17678        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17679    });
17680    cx.executor().run_until_parked();
17681
17682    cx.assert_state_with_diff(
17683        "
17684            «aaa
17685          - bbb
17686            ccc
17687            ddd
17688
17689            ggg
17690            hhh
17691
17692
17693            lll
17694            mmm
17695          - nnn
17696          + NNN
17697
17698            qqq
17699            rrr
17700
17701            uuu
17702            111
17703            222
17704            333
17705
17706          + 666
17707            777
17708
17709            000
17710            !!!ˇ»"
17711            .unindent(),
17712    );
17713}
17714
17715#[gpui::test]
17716async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17717    init_test(cx, |_| {});
17718
17719    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17720    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17721
17722    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17723    let multi_buffer = cx.new(|cx| {
17724        let mut multibuffer = MultiBuffer::new(ReadWrite);
17725        multibuffer.push_excerpts(
17726            buffer.clone(),
17727            [
17728                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17729                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17730                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17731            ],
17732            cx,
17733        );
17734        multibuffer
17735    });
17736
17737    let editor =
17738        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17739    editor
17740        .update(cx, |editor, _window, cx| {
17741            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17742            editor
17743                .buffer
17744                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17745        })
17746        .unwrap();
17747
17748    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17749    cx.run_until_parked();
17750
17751    cx.update_editor(|editor, window, cx| {
17752        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17753    });
17754    cx.executor().run_until_parked();
17755
17756    // When the start of a hunk coincides with the start of its excerpt,
17757    // the hunk is expanded. When the start of a a hunk is earlier than
17758    // the start of its excerpt, the hunk is not expanded.
17759    cx.assert_state_with_diff(
17760        "
17761            ˇaaa
17762          - bbb
17763          + BBB
17764
17765          - ddd
17766          - eee
17767          + DDD
17768          + EEE
17769            fff
17770
17771            iii
17772        "
17773        .unindent(),
17774    );
17775}
17776
17777#[gpui::test]
17778async fn test_edits_around_expanded_insertion_hunks(
17779    executor: BackgroundExecutor,
17780    cx: &mut TestAppContext,
17781) {
17782    init_test(cx, |_| {});
17783
17784    let mut cx = EditorTestContext::new(cx).await;
17785
17786    let diff_base = r#"
17787        use some::mod1;
17788        use some::mod2;
17789
17790        const A: u32 = 42;
17791
17792        fn main() {
17793            println!("hello");
17794
17795            println!("world");
17796        }
17797        "#
17798    .unindent();
17799    executor.run_until_parked();
17800    cx.set_state(
17801        &r#"
17802        use some::mod1;
17803        use some::mod2;
17804
17805        const A: u32 = 42;
17806        const B: u32 = 42;
17807        const C: u32 = 42;
17808        ˇ
17809
17810        fn main() {
17811            println!("hello");
17812
17813            println!("world");
17814        }
17815        "#
17816        .unindent(),
17817    );
17818
17819    cx.set_head_text(&diff_base);
17820    executor.run_until_parked();
17821
17822    cx.update_editor(|editor, window, cx| {
17823        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17824    });
17825    executor.run_until_parked();
17826
17827    cx.assert_state_with_diff(
17828        r#"
17829        use some::mod1;
17830        use some::mod2;
17831
17832        const A: u32 = 42;
17833      + const B: u32 = 42;
17834      + const C: u32 = 42;
17835      + ˇ
17836
17837        fn main() {
17838            println!("hello");
17839
17840            println!("world");
17841        }
17842      "#
17843        .unindent(),
17844    );
17845
17846    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17847    executor.run_until_parked();
17848
17849    cx.assert_state_with_diff(
17850        r#"
17851        use some::mod1;
17852        use some::mod2;
17853
17854        const A: u32 = 42;
17855      + const B: u32 = 42;
17856      + const C: u32 = 42;
17857      + const D: u32 = 42;
17858      + ˇ
17859
17860        fn main() {
17861            println!("hello");
17862
17863            println!("world");
17864        }
17865      "#
17866        .unindent(),
17867    );
17868
17869    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17870    executor.run_until_parked();
17871
17872    cx.assert_state_with_diff(
17873        r#"
17874        use some::mod1;
17875        use some::mod2;
17876
17877        const A: u32 = 42;
17878      + const B: u32 = 42;
17879      + const C: u32 = 42;
17880      + const D: u32 = 42;
17881      + const E: u32 = 42;
17882      + ˇ
17883
17884        fn main() {
17885            println!("hello");
17886
17887            println!("world");
17888        }
17889      "#
17890        .unindent(),
17891    );
17892
17893    cx.update_editor(|editor, window, cx| {
17894        editor.delete_line(&DeleteLine, window, cx);
17895    });
17896    executor.run_until_parked();
17897
17898    cx.assert_state_with_diff(
17899        r#"
17900        use some::mod1;
17901        use some::mod2;
17902
17903        const A: u32 = 42;
17904      + const B: u32 = 42;
17905      + const C: u32 = 42;
17906      + const D: u32 = 42;
17907      + const E: u32 = 42;
17908        ˇ
17909        fn main() {
17910            println!("hello");
17911
17912            println!("world");
17913        }
17914      "#
17915        .unindent(),
17916    );
17917
17918    cx.update_editor(|editor, window, cx| {
17919        editor.move_up(&MoveUp, window, cx);
17920        editor.delete_line(&DeleteLine, window, cx);
17921        editor.move_up(&MoveUp, window, cx);
17922        editor.delete_line(&DeleteLine, window, cx);
17923        editor.move_up(&MoveUp, window, cx);
17924        editor.delete_line(&DeleteLine, window, cx);
17925    });
17926    executor.run_until_parked();
17927    cx.assert_state_with_diff(
17928        r#"
17929        use some::mod1;
17930        use some::mod2;
17931
17932        const A: u32 = 42;
17933      + const B: u32 = 42;
17934        ˇ
17935        fn main() {
17936            println!("hello");
17937
17938            println!("world");
17939        }
17940      "#
17941        .unindent(),
17942    );
17943
17944    cx.update_editor(|editor, window, cx| {
17945        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17946        editor.delete_line(&DeleteLine, window, cx);
17947    });
17948    executor.run_until_parked();
17949    cx.assert_state_with_diff(
17950        r#"
17951        ˇ
17952        fn main() {
17953            println!("hello");
17954
17955            println!("world");
17956        }
17957      "#
17958        .unindent(),
17959    );
17960}
17961
17962#[gpui::test]
17963async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17964    init_test(cx, |_| {});
17965
17966    let mut cx = EditorTestContext::new(cx).await;
17967    cx.set_head_text(indoc! { "
17968        one
17969        two
17970        three
17971        four
17972        five
17973        "
17974    });
17975    cx.set_state(indoc! { "
17976        one
17977        ˇthree
17978        five
17979    "});
17980    cx.run_until_parked();
17981    cx.update_editor(|editor, window, cx| {
17982        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17983    });
17984    cx.assert_state_with_diff(
17985        indoc! { "
17986        one
17987      - two
17988        ˇthree
17989      - four
17990        five
17991    "}
17992        .to_string(),
17993    );
17994    cx.update_editor(|editor, window, cx| {
17995        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17996    });
17997
17998    cx.assert_state_with_diff(
17999        indoc! { "
18000        one
18001        ˇthree
18002        five
18003    "}
18004        .to_string(),
18005    );
18006
18007    cx.set_state(indoc! { "
18008        one
18009        ˇTWO
18010        three
18011        four
18012        five
18013    "});
18014    cx.run_until_parked();
18015    cx.update_editor(|editor, window, cx| {
18016        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18017    });
18018
18019    cx.assert_state_with_diff(
18020        indoc! { "
18021            one
18022          - two
18023          + ˇTWO
18024            three
18025            four
18026            five
18027        "}
18028        .to_string(),
18029    );
18030    cx.update_editor(|editor, window, cx| {
18031        editor.move_up(&Default::default(), window, cx);
18032        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18033    });
18034    cx.assert_state_with_diff(
18035        indoc! { "
18036            one
18037            ˇTWO
18038            three
18039            four
18040            five
18041        "}
18042        .to_string(),
18043    );
18044}
18045
18046#[gpui::test]
18047async fn test_edits_around_expanded_deletion_hunks(
18048    executor: BackgroundExecutor,
18049    cx: &mut TestAppContext,
18050) {
18051    init_test(cx, |_| {});
18052
18053    let mut cx = EditorTestContext::new(cx).await;
18054
18055    let diff_base = r#"
18056        use some::mod1;
18057        use some::mod2;
18058
18059        const A: u32 = 42;
18060        const B: u32 = 42;
18061        const C: u32 = 42;
18062
18063
18064        fn main() {
18065            println!("hello");
18066
18067            println!("world");
18068        }
18069    "#
18070    .unindent();
18071    executor.run_until_parked();
18072    cx.set_state(
18073        &r#"
18074        use some::mod1;
18075        use some::mod2;
18076
18077        ˇconst B: u32 = 42;
18078        const C: u32 = 42;
18079
18080
18081        fn main() {
18082            println!("hello");
18083
18084            println!("world");
18085        }
18086        "#
18087        .unindent(),
18088    );
18089
18090    cx.set_head_text(&diff_base);
18091    executor.run_until_parked();
18092
18093    cx.update_editor(|editor, window, cx| {
18094        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18095    });
18096    executor.run_until_parked();
18097
18098    cx.assert_state_with_diff(
18099        r#"
18100        use some::mod1;
18101        use some::mod2;
18102
18103      - const A: u32 = 42;
18104        ˇconst B: u32 = 42;
18105        const C: u32 = 42;
18106
18107
18108        fn main() {
18109            println!("hello");
18110
18111            println!("world");
18112        }
18113      "#
18114        .unindent(),
18115    );
18116
18117    cx.update_editor(|editor, window, cx| {
18118        editor.delete_line(&DeleteLine, window, cx);
18119    });
18120    executor.run_until_parked();
18121    cx.assert_state_with_diff(
18122        r#"
18123        use some::mod1;
18124        use some::mod2;
18125
18126      - const A: u32 = 42;
18127      - const B: u32 = 42;
18128        ˇconst C: u32 = 42;
18129
18130
18131        fn main() {
18132            println!("hello");
18133
18134            println!("world");
18135        }
18136      "#
18137        .unindent(),
18138    );
18139
18140    cx.update_editor(|editor, window, cx| {
18141        editor.delete_line(&DeleteLine, window, cx);
18142    });
18143    executor.run_until_parked();
18144    cx.assert_state_with_diff(
18145        r#"
18146        use some::mod1;
18147        use some::mod2;
18148
18149      - const A: u32 = 42;
18150      - const B: u32 = 42;
18151      - const C: u32 = 42;
18152        ˇ
18153
18154        fn main() {
18155            println!("hello");
18156
18157            println!("world");
18158        }
18159      "#
18160        .unindent(),
18161    );
18162
18163    cx.update_editor(|editor, window, cx| {
18164        editor.handle_input("replacement", window, cx);
18165    });
18166    executor.run_until_parked();
18167    cx.assert_state_with_diff(
18168        r#"
18169        use some::mod1;
18170        use some::mod2;
18171
18172      - const A: u32 = 42;
18173      - const B: u32 = 42;
18174      - const C: u32 = 42;
18175      -
18176      + replacementˇ
18177
18178        fn main() {
18179            println!("hello");
18180
18181            println!("world");
18182        }
18183      "#
18184        .unindent(),
18185    );
18186}
18187
18188#[gpui::test]
18189async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18190    init_test(cx, |_| {});
18191
18192    let mut cx = EditorTestContext::new(cx).await;
18193
18194    let base_text = r#"
18195        one
18196        two
18197        three
18198        four
18199        five
18200    "#
18201    .unindent();
18202    executor.run_until_parked();
18203    cx.set_state(
18204        &r#"
18205        one
18206        two
18207        fˇour
18208        five
18209        "#
18210        .unindent(),
18211    );
18212
18213    cx.set_head_text(&base_text);
18214    executor.run_until_parked();
18215
18216    cx.update_editor(|editor, window, cx| {
18217        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18218    });
18219    executor.run_until_parked();
18220
18221    cx.assert_state_with_diff(
18222        r#"
18223          one
18224          two
18225        - three
18226          fˇour
18227          five
18228        "#
18229        .unindent(),
18230    );
18231
18232    cx.update_editor(|editor, window, cx| {
18233        editor.backspace(&Backspace, window, cx);
18234        editor.backspace(&Backspace, window, cx);
18235    });
18236    executor.run_until_parked();
18237    cx.assert_state_with_diff(
18238        r#"
18239          one
18240          two
18241        - threeˇ
18242        - four
18243        + our
18244          five
18245        "#
18246        .unindent(),
18247    );
18248}
18249
18250#[gpui::test]
18251async fn test_edit_after_expanded_modification_hunk(
18252    executor: BackgroundExecutor,
18253    cx: &mut TestAppContext,
18254) {
18255    init_test(cx, |_| {});
18256
18257    let mut cx = EditorTestContext::new(cx).await;
18258
18259    let diff_base = r#"
18260        use some::mod1;
18261        use some::mod2;
18262
18263        const A: u32 = 42;
18264        const B: u32 = 42;
18265        const C: u32 = 42;
18266        const D: u32 = 42;
18267
18268
18269        fn main() {
18270            println!("hello");
18271
18272            println!("world");
18273        }"#
18274    .unindent();
18275
18276    cx.set_state(
18277        &r#"
18278        use some::mod1;
18279        use some::mod2;
18280
18281        const A: u32 = 42;
18282        const B: u32 = 42;
18283        const C: u32 = 43ˇ
18284        const D: u32 = 42;
18285
18286
18287        fn main() {
18288            println!("hello");
18289
18290            println!("world");
18291        }"#
18292        .unindent(),
18293    );
18294
18295    cx.set_head_text(&diff_base);
18296    executor.run_until_parked();
18297    cx.update_editor(|editor, window, cx| {
18298        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18299    });
18300    executor.run_until_parked();
18301
18302    cx.assert_state_with_diff(
18303        r#"
18304        use some::mod1;
18305        use some::mod2;
18306
18307        const A: u32 = 42;
18308        const B: u32 = 42;
18309      - const C: u32 = 42;
18310      + const C: u32 = 43ˇ
18311        const D: u32 = 42;
18312
18313
18314        fn main() {
18315            println!("hello");
18316
18317            println!("world");
18318        }"#
18319        .unindent(),
18320    );
18321
18322    cx.update_editor(|editor, window, cx| {
18323        editor.handle_input("\nnew_line\n", window, cx);
18324    });
18325    executor.run_until_parked();
18326
18327    cx.assert_state_with_diff(
18328        r#"
18329        use some::mod1;
18330        use some::mod2;
18331
18332        const A: u32 = 42;
18333        const B: u32 = 42;
18334      - const C: u32 = 42;
18335      + const C: u32 = 43
18336      + new_line
18337      + ˇ
18338        const D: u32 = 42;
18339
18340
18341        fn main() {
18342            println!("hello");
18343
18344            println!("world");
18345        }"#
18346        .unindent(),
18347    );
18348}
18349
18350#[gpui::test]
18351async fn test_stage_and_unstage_added_file_hunk(
18352    executor: BackgroundExecutor,
18353    cx: &mut TestAppContext,
18354) {
18355    init_test(cx, |_| {});
18356
18357    let mut cx = EditorTestContext::new(cx).await;
18358    cx.update_editor(|editor, _, cx| {
18359        editor.set_expand_all_diff_hunks(cx);
18360    });
18361
18362    let working_copy = r#"
18363            ˇfn main() {
18364                println!("hello, world!");
18365            }
18366        "#
18367    .unindent();
18368
18369    cx.set_state(&working_copy);
18370    executor.run_until_parked();
18371
18372    cx.assert_state_with_diff(
18373        r#"
18374            + ˇfn main() {
18375            +     println!("hello, world!");
18376            + }
18377        "#
18378        .unindent(),
18379    );
18380    cx.assert_index_text(None);
18381
18382    cx.update_editor(|editor, window, cx| {
18383        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18384    });
18385    executor.run_until_parked();
18386    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18387    cx.assert_state_with_diff(
18388        r#"
18389            + ˇfn main() {
18390            +     println!("hello, world!");
18391            + }
18392        "#
18393        .unindent(),
18394    );
18395
18396    cx.update_editor(|editor, window, cx| {
18397        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18398    });
18399    executor.run_until_parked();
18400    cx.assert_index_text(None);
18401}
18402
18403async fn setup_indent_guides_editor(
18404    text: &str,
18405    cx: &mut TestAppContext,
18406) -> (BufferId, EditorTestContext) {
18407    init_test(cx, |_| {});
18408
18409    let mut cx = EditorTestContext::new(cx).await;
18410
18411    let buffer_id = cx.update_editor(|editor, window, cx| {
18412        editor.set_text(text, window, cx);
18413        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18414
18415        buffer_ids[0]
18416    });
18417
18418    (buffer_id, cx)
18419}
18420
18421fn assert_indent_guides(
18422    range: Range<u32>,
18423    expected: Vec<IndentGuide>,
18424    active_indices: Option<Vec<usize>>,
18425    cx: &mut EditorTestContext,
18426) {
18427    let indent_guides = cx.update_editor(|editor, window, cx| {
18428        let snapshot = editor.snapshot(window, cx).display_snapshot;
18429        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18430            editor,
18431            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18432            true,
18433            &snapshot,
18434            cx,
18435        );
18436
18437        indent_guides.sort_by(|a, b| {
18438            a.depth.cmp(&b.depth).then(
18439                a.start_row
18440                    .cmp(&b.start_row)
18441                    .then(a.end_row.cmp(&b.end_row)),
18442            )
18443        });
18444        indent_guides
18445    });
18446
18447    if let Some(expected) = active_indices {
18448        let active_indices = cx.update_editor(|editor, window, cx| {
18449            let snapshot = editor.snapshot(window, cx).display_snapshot;
18450            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18451        });
18452
18453        assert_eq!(
18454            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18455            expected,
18456            "Active indent guide indices do not match"
18457        );
18458    }
18459
18460    assert_eq!(indent_guides, expected, "Indent guides do not match");
18461}
18462
18463fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18464    IndentGuide {
18465        buffer_id,
18466        start_row: MultiBufferRow(start_row),
18467        end_row: MultiBufferRow(end_row),
18468        depth,
18469        tab_size: 4,
18470        settings: IndentGuideSettings {
18471            enabled: true,
18472            line_width: 1,
18473            active_line_width: 1,
18474            ..Default::default()
18475        },
18476    }
18477}
18478
18479#[gpui::test]
18480async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18481    let (buffer_id, mut cx) = setup_indent_guides_editor(
18482        &"
18483        fn main() {
18484            let a = 1;
18485        }"
18486        .unindent(),
18487        cx,
18488    )
18489    .await;
18490
18491    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18492}
18493
18494#[gpui::test]
18495async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18496    let (buffer_id, mut cx) = setup_indent_guides_editor(
18497        &"
18498        fn main() {
18499            let a = 1;
18500            let b = 2;
18501        }"
18502        .unindent(),
18503        cx,
18504    )
18505    .await;
18506
18507    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18508}
18509
18510#[gpui::test]
18511async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18512    let (buffer_id, mut cx) = setup_indent_guides_editor(
18513        &"
18514        fn main() {
18515            let a = 1;
18516            if a == 3 {
18517                let b = 2;
18518            } else {
18519                let c = 3;
18520            }
18521        }"
18522        .unindent(),
18523        cx,
18524    )
18525    .await;
18526
18527    assert_indent_guides(
18528        0..8,
18529        vec![
18530            indent_guide(buffer_id, 1, 6, 0),
18531            indent_guide(buffer_id, 3, 3, 1),
18532            indent_guide(buffer_id, 5, 5, 1),
18533        ],
18534        None,
18535        &mut cx,
18536    );
18537}
18538
18539#[gpui::test]
18540async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18541    let (buffer_id, mut cx) = setup_indent_guides_editor(
18542        &"
18543        fn main() {
18544            let a = 1;
18545                let b = 2;
18546            let c = 3;
18547        }"
18548        .unindent(),
18549        cx,
18550    )
18551    .await;
18552
18553    assert_indent_guides(
18554        0..5,
18555        vec![
18556            indent_guide(buffer_id, 1, 3, 0),
18557            indent_guide(buffer_id, 2, 2, 1),
18558        ],
18559        None,
18560        &mut cx,
18561    );
18562}
18563
18564#[gpui::test]
18565async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18566    let (buffer_id, mut cx) = setup_indent_guides_editor(
18567        &"
18568        fn main() {
18569            let a = 1;
18570
18571            let c = 3;
18572        }"
18573        .unindent(),
18574        cx,
18575    )
18576    .await;
18577
18578    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18579}
18580
18581#[gpui::test]
18582async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18583    let (buffer_id, mut cx) = setup_indent_guides_editor(
18584        &"
18585        fn main() {
18586            let a = 1;
18587
18588            let c = 3;
18589
18590            if a == 3 {
18591                let b = 2;
18592            } else {
18593                let c = 3;
18594            }
18595        }"
18596        .unindent(),
18597        cx,
18598    )
18599    .await;
18600
18601    assert_indent_guides(
18602        0..11,
18603        vec![
18604            indent_guide(buffer_id, 1, 9, 0),
18605            indent_guide(buffer_id, 6, 6, 1),
18606            indent_guide(buffer_id, 8, 8, 1),
18607        ],
18608        None,
18609        &mut cx,
18610    );
18611}
18612
18613#[gpui::test]
18614async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18615    let (buffer_id, mut cx) = setup_indent_guides_editor(
18616        &"
18617        fn main() {
18618            let a = 1;
18619
18620            let c = 3;
18621
18622            if a == 3 {
18623                let b = 2;
18624            } else {
18625                let c = 3;
18626            }
18627        }"
18628        .unindent(),
18629        cx,
18630    )
18631    .await;
18632
18633    assert_indent_guides(
18634        1..11,
18635        vec![
18636            indent_guide(buffer_id, 1, 9, 0),
18637            indent_guide(buffer_id, 6, 6, 1),
18638            indent_guide(buffer_id, 8, 8, 1),
18639        ],
18640        None,
18641        &mut cx,
18642    );
18643}
18644
18645#[gpui::test]
18646async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18647    let (buffer_id, mut cx) = setup_indent_guides_editor(
18648        &"
18649        fn main() {
18650            let a = 1;
18651
18652            let c = 3;
18653
18654            if a == 3 {
18655                let b = 2;
18656            } else {
18657                let c = 3;
18658            }
18659        }"
18660        .unindent(),
18661        cx,
18662    )
18663    .await;
18664
18665    assert_indent_guides(
18666        1..10,
18667        vec![
18668            indent_guide(buffer_id, 1, 9, 0),
18669            indent_guide(buffer_id, 6, 6, 1),
18670            indent_guide(buffer_id, 8, 8, 1),
18671        ],
18672        None,
18673        &mut cx,
18674    );
18675}
18676
18677#[gpui::test]
18678async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18679    let (buffer_id, mut cx) = setup_indent_guides_editor(
18680        &"
18681        fn main() {
18682            if a {
18683                b(
18684                    c,
18685                    d,
18686                )
18687            } else {
18688                e(
18689                    f
18690                )
18691            }
18692        }"
18693        .unindent(),
18694        cx,
18695    )
18696    .await;
18697
18698    assert_indent_guides(
18699        0..11,
18700        vec![
18701            indent_guide(buffer_id, 1, 10, 0),
18702            indent_guide(buffer_id, 2, 5, 1),
18703            indent_guide(buffer_id, 7, 9, 1),
18704            indent_guide(buffer_id, 3, 4, 2),
18705            indent_guide(buffer_id, 8, 8, 2),
18706        ],
18707        None,
18708        &mut cx,
18709    );
18710
18711    cx.update_editor(|editor, window, cx| {
18712        editor.fold_at(MultiBufferRow(2), window, cx);
18713        assert_eq!(
18714            editor.display_text(cx),
18715            "
18716            fn main() {
18717                if a {
18718                    b(⋯
18719                    )
18720                } else {
18721                    e(
18722                        f
18723                    )
18724                }
18725            }"
18726            .unindent()
18727        );
18728    });
18729
18730    assert_indent_guides(
18731        0..11,
18732        vec![
18733            indent_guide(buffer_id, 1, 10, 0),
18734            indent_guide(buffer_id, 2, 5, 1),
18735            indent_guide(buffer_id, 7, 9, 1),
18736            indent_guide(buffer_id, 8, 8, 2),
18737        ],
18738        None,
18739        &mut cx,
18740    );
18741}
18742
18743#[gpui::test]
18744async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18745    let (buffer_id, mut cx) = setup_indent_guides_editor(
18746        &"
18747        block1
18748            block2
18749                block3
18750                    block4
18751            block2
18752        block1
18753        block1"
18754            .unindent(),
18755        cx,
18756    )
18757    .await;
18758
18759    assert_indent_guides(
18760        1..10,
18761        vec![
18762            indent_guide(buffer_id, 1, 4, 0),
18763            indent_guide(buffer_id, 2, 3, 1),
18764            indent_guide(buffer_id, 3, 3, 2),
18765        ],
18766        None,
18767        &mut cx,
18768    );
18769}
18770
18771#[gpui::test]
18772async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18773    let (buffer_id, mut cx) = setup_indent_guides_editor(
18774        &"
18775        block1
18776            block2
18777                block3
18778
18779        block1
18780        block1"
18781            .unindent(),
18782        cx,
18783    )
18784    .await;
18785
18786    assert_indent_guides(
18787        0..6,
18788        vec![
18789            indent_guide(buffer_id, 1, 2, 0),
18790            indent_guide(buffer_id, 2, 2, 1),
18791        ],
18792        None,
18793        &mut cx,
18794    );
18795}
18796
18797#[gpui::test]
18798async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18799    let (buffer_id, mut cx) = setup_indent_guides_editor(
18800        &"
18801        function component() {
18802        \treturn (
18803        \t\t\t
18804        \t\t<div>
18805        \t\t\t<abc></abc>
18806        \t\t</div>
18807        \t)
18808        }"
18809        .unindent(),
18810        cx,
18811    )
18812    .await;
18813
18814    assert_indent_guides(
18815        0..8,
18816        vec![
18817            indent_guide(buffer_id, 1, 6, 0),
18818            indent_guide(buffer_id, 2, 5, 1),
18819            indent_guide(buffer_id, 4, 4, 2),
18820        ],
18821        None,
18822        &mut cx,
18823    );
18824}
18825
18826#[gpui::test]
18827async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18828    let (buffer_id, mut cx) = setup_indent_guides_editor(
18829        &"
18830        function component() {
18831        \treturn (
18832        \t
18833        \t\t<div>
18834        \t\t\t<abc></abc>
18835        \t\t</div>
18836        \t)
18837        }"
18838        .unindent(),
18839        cx,
18840    )
18841    .await;
18842
18843    assert_indent_guides(
18844        0..8,
18845        vec![
18846            indent_guide(buffer_id, 1, 6, 0),
18847            indent_guide(buffer_id, 2, 5, 1),
18848            indent_guide(buffer_id, 4, 4, 2),
18849        ],
18850        None,
18851        &mut cx,
18852    );
18853}
18854
18855#[gpui::test]
18856async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18857    let (buffer_id, mut cx) = setup_indent_guides_editor(
18858        &"
18859        block1
18860
18861
18862
18863            block2
18864        "
18865        .unindent(),
18866        cx,
18867    )
18868    .await;
18869
18870    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18871}
18872
18873#[gpui::test]
18874async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18875    let (buffer_id, mut cx) = setup_indent_guides_editor(
18876        &"
18877        def a:
18878        \tb = 3
18879        \tif True:
18880        \t\tc = 4
18881        \t\td = 5
18882        \tprint(b)
18883        "
18884        .unindent(),
18885        cx,
18886    )
18887    .await;
18888
18889    assert_indent_guides(
18890        0..6,
18891        vec![
18892            indent_guide(buffer_id, 1, 5, 0),
18893            indent_guide(buffer_id, 3, 4, 1),
18894        ],
18895        None,
18896        &mut cx,
18897    );
18898}
18899
18900#[gpui::test]
18901async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18902    let (buffer_id, mut cx) = setup_indent_guides_editor(
18903        &"
18904    fn main() {
18905        let a = 1;
18906    }"
18907        .unindent(),
18908        cx,
18909    )
18910    .await;
18911
18912    cx.update_editor(|editor, window, cx| {
18913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18914            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18915        });
18916    });
18917
18918    assert_indent_guides(
18919        0..3,
18920        vec![indent_guide(buffer_id, 1, 1, 0)],
18921        Some(vec![0]),
18922        &mut cx,
18923    );
18924}
18925
18926#[gpui::test]
18927async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18928    let (buffer_id, mut cx) = setup_indent_guides_editor(
18929        &"
18930    fn main() {
18931        if 1 == 2 {
18932            let a = 1;
18933        }
18934    }"
18935        .unindent(),
18936        cx,
18937    )
18938    .await;
18939
18940    cx.update_editor(|editor, window, cx| {
18941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18942            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18943        });
18944    });
18945
18946    assert_indent_guides(
18947        0..4,
18948        vec![
18949            indent_guide(buffer_id, 1, 3, 0),
18950            indent_guide(buffer_id, 2, 2, 1),
18951        ],
18952        Some(vec![1]),
18953        &mut cx,
18954    );
18955
18956    cx.update_editor(|editor, window, cx| {
18957        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18958            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18959        });
18960    });
18961
18962    assert_indent_guides(
18963        0..4,
18964        vec![
18965            indent_guide(buffer_id, 1, 3, 0),
18966            indent_guide(buffer_id, 2, 2, 1),
18967        ],
18968        Some(vec![1]),
18969        &mut cx,
18970    );
18971
18972    cx.update_editor(|editor, window, cx| {
18973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18974            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18975        });
18976    });
18977
18978    assert_indent_guides(
18979        0..4,
18980        vec![
18981            indent_guide(buffer_id, 1, 3, 0),
18982            indent_guide(buffer_id, 2, 2, 1),
18983        ],
18984        Some(vec![0]),
18985        &mut cx,
18986    );
18987}
18988
18989#[gpui::test]
18990async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18991    let (buffer_id, mut cx) = setup_indent_guides_editor(
18992        &"
18993    fn main() {
18994        let a = 1;
18995
18996        let b = 2;
18997    }"
18998        .unindent(),
18999        cx,
19000    )
19001    .await;
19002
19003    cx.update_editor(|editor, window, cx| {
19004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19005            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19006        });
19007    });
19008
19009    assert_indent_guides(
19010        0..5,
19011        vec![indent_guide(buffer_id, 1, 3, 0)],
19012        Some(vec![0]),
19013        &mut cx,
19014    );
19015}
19016
19017#[gpui::test]
19018async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19019    let (buffer_id, mut cx) = setup_indent_guides_editor(
19020        &"
19021    def m:
19022        a = 1
19023        pass"
19024            .unindent(),
19025        cx,
19026    )
19027    .await;
19028
19029    cx.update_editor(|editor, window, cx| {
19030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19031            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19032        });
19033    });
19034
19035    assert_indent_guides(
19036        0..3,
19037        vec![indent_guide(buffer_id, 1, 2, 0)],
19038        Some(vec![0]),
19039        &mut cx,
19040    );
19041}
19042
19043#[gpui::test]
19044async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19045    init_test(cx, |_| {});
19046    let mut cx = EditorTestContext::new(cx).await;
19047    let text = indoc! {
19048        "
19049        impl A {
19050            fn b() {
19051                0;
19052                3;
19053                5;
19054                6;
19055                7;
19056            }
19057        }
19058        "
19059    };
19060    let base_text = indoc! {
19061        "
19062        impl A {
19063            fn b() {
19064                0;
19065                1;
19066                2;
19067                3;
19068                4;
19069            }
19070            fn c() {
19071                5;
19072                6;
19073                7;
19074            }
19075        }
19076        "
19077    };
19078
19079    cx.update_editor(|editor, window, cx| {
19080        editor.set_text(text, window, cx);
19081
19082        editor.buffer().update(cx, |multibuffer, cx| {
19083            let buffer = multibuffer.as_singleton().unwrap();
19084            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19085
19086            multibuffer.set_all_diff_hunks_expanded(cx);
19087            multibuffer.add_diff(diff, cx);
19088
19089            buffer.read(cx).remote_id()
19090        })
19091    });
19092    cx.run_until_parked();
19093
19094    cx.assert_state_with_diff(
19095        indoc! { "
19096          impl A {
19097              fn b() {
19098                  0;
19099        -         1;
19100        -         2;
19101                  3;
19102        -         4;
19103        -     }
19104        -     fn c() {
19105                  5;
19106                  6;
19107                  7;
19108              }
19109          }
19110          ˇ"
19111        }
19112        .to_string(),
19113    );
19114
19115    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19116        editor
19117            .snapshot(window, cx)
19118            .buffer_snapshot
19119            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19120            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19121            .collect::<Vec<_>>()
19122    });
19123    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19124    assert_eq!(
19125        actual_guides,
19126        vec![
19127            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19128            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19129            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19130        ]
19131    );
19132}
19133
19134#[gpui::test]
19135async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19136    init_test(cx, |_| {});
19137    let mut cx = EditorTestContext::new(cx).await;
19138
19139    let diff_base = r#"
19140        a
19141        b
19142        c
19143        "#
19144    .unindent();
19145
19146    cx.set_state(
19147        &r#"
19148        ˇA
19149        b
19150        C
19151        "#
19152        .unindent(),
19153    );
19154    cx.set_head_text(&diff_base);
19155    cx.update_editor(|editor, window, cx| {
19156        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19157    });
19158    executor.run_until_parked();
19159
19160    let both_hunks_expanded = r#"
19161        - a
19162        + ˇA
19163          b
19164        - c
19165        + C
19166        "#
19167    .unindent();
19168
19169    cx.assert_state_with_diff(both_hunks_expanded.clone());
19170
19171    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19172        let snapshot = editor.snapshot(window, cx);
19173        let hunks = editor
19174            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19175            .collect::<Vec<_>>();
19176        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19177        let buffer_id = hunks[0].buffer_id;
19178        hunks
19179            .into_iter()
19180            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19181            .collect::<Vec<_>>()
19182    });
19183    assert_eq!(hunk_ranges.len(), 2);
19184
19185    cx.update_editor(|editor, _, cx| {
19186        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19187    });
19188    executor.run_until_parked();
19189
19190    let second_hunk_expanded = r#"
19191          ˇA
19192          b
19193        - c
19194        + C
19195        "#
19196    .unindent();
19197
19198    cx.assert_state_with_diff(second_hunk_expanded);
19199
19200    cx.update_editor(|editor, _, cx| {
19201        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19202    });
19203    executor.run_until_parked();
19204
19205    cx.assert_state_with_diff(both_hunks_expanded.clone());
19206
19207    cx.update_editor(|editor, _, cx| {
19208        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19209    });
19210    executor.run_until_parked();
19211
19212    let first_hunk_expanded = r#"
19213        - a
19214        + ˇA
19215          b
19216          C
19217        "#
19218    .unindent();
19219
19220    cx.assert_state_with_diff(first_hunk_expanded);
19221
19222    cx.update_editor(|editor, _, cx| {
19223        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19224    });
19225    executor.run_until_parked();
19226
19227    cx.assert_state_with_diff(both_hunks_expanded);
19228
19229    cx.set_state(
19230        &r#"
19231        ˇA
19232        b
19233        "#
19234        .unindent(),
19235    );
19236    cx.run_until_parked();
19237
19238    // TODO this cursor position seems bad
19239    cx.assert_state_with_diff(
19240        r#"
19241        - ˇa
19242        + A
19243          b
19244        "#
19245        .unindent(),
19246    );
19247
19248    cx.update_editor(|editor, window, cx| {
19249        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19250    });
19251
19252    cx.assert_state_with_diff(
19253        r#"
19254            - ˇa
19255            + A
19256              b
19257            - c
19258            "#
19259        .unindent(),
19260    );
19261
19262    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19263        let snapshot = editor.snapshot(window, cx);
19264        let hunks = editor
19265            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19266            .collect::<Vec<_>>();
19267        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19268        let buffer_id = hunks[0].buffer_id;
19269        hunks
19270            .into_iter()
19271            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19272            .collect::<Vec<_>>()
19273    });
19274    assert_eq!(hunk_ranges.len(), 2);
19275
19276    cx.update_editor(|editor, _, cx| {
19277        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19278    });
19279    executor.run_until_parked();
19280
19281    cx.assert_state_with_diff(
19282        r#"
19283        - ˇa
19284        + A
19285          b
19286        "#
19287        .unindent(),
19288    );
19289}
19290
19291#[gpui::test]
19292async fn test_toggle_deletion_hunk_at_start_of_file(
19293    executor: BackgroundExecutor,
19294    cx: &mut TestAppContext,
19295) {
19296    init_test(cx, |_| {});
19297    let mut cx = EditorTestContext::new(cx).await;
19298
19299    let diff_base = r#"
19300        a
19301        b
19302        c
19303        "#
19304    .unindent();
19305
19306    cx.set_state(
19307        &r#"
19308        ˇb
19309        c
19310        "#
19311        .unindent(),
19312    );
19313    cx.set_head_text(&diff_base);
19314    cx.update_editor(|editor, window, cx| {
19315        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19316    });
19317    executor.run_until_parked();
19318
19319    let hunk_expanded = r#"
19320        - a
19321          ˇb
19322          c
19323        "#
19324    .unindent();
19325
19326    cx.assert_state_with_diff(hunk_expanded.clone());
19327
19328    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19329        let snapshot = editor.snapshot(window, cx);
19330        let hunks = editor
19331            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19332            .collect::<Vec<_>>();
19333        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19334        let buffer_id = hunks[0].buffer_id;
19335        hunks
19336            .into_iter()
19337            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19338            .collect::<Vec<_>>()
19339    });
19340    assert_eq!(hunk_ranges.len(), 1);
19341
19342    cx.update_editor(|editor, _, cx| {
19343        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19344    });
19345    executor.run_until_parked();
19346
19347    let hunk_collapsed = r#"
19348          ˇb
19349          c
19350        "#
19351    .unindent();
19352
19353    cx.assert_state_with_diff(hunk_collapsed);
19354
19355    cx.update_editor(|editor, _, cx| {
19356        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19357    });
19358    executor.run_until_parked();
19359
19360    cx.assert_state_with_diff(hunk_expanded.clone());
19361}
19362
19363#[gpui::test]
19364async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19365    init_test(cx, |_| {});
19366
19367    let fs = FakeFs::new(cx.executor());
19368    fs.insert_tree(
19369        path!("/test"),
19370        json!({
19371            ".git": {},
19372            "file-1": "ONE\n",
19373            "file-2": "TWO\n",
19374            "file-3": "THREE\n",
19375        }),
19376    )
19377    .await;
19378
19379    fs.set_head_for_repo(
19380        path!("/test/.git").as_ref(),
19381        &[
19382            ("file-1".into(), "one\n".into()),
19383            ("file-2".into(), "two\n".into()),
19384            ("file-3".into(), "three\n".into()),
19385        ],
19386        "deadbeef",
19387    );
19388
19389    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19390    let mut buffers = vec![];
19391    for i in 1..=3 {
19392        let buffer = project
19393            .update(cx, |project, cx| {
19394                let path = format!(path!("/test/file-{}"), i);
19395                project.open_local_buffer(path, cx)
19396            })
19397            .await
19398            .unwrap();
19399        buffers.push(buffer);
19400    }
19401
19402    let multibuffer = cx.new(|cx| {
19403        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19404        multibuffer.set_all_diff_hunks_expanded(cx);
19405        for buffer in &buffers {
19406            let snapshot = buffer.read(cx).snapshot();
19407            multibuffer.set_excerpts_for_path(
19408                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19409                buffer.clone(),
19410                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19411                DEFAULT_MULTIBUFFER_CONTEXT,
19412                cx,
19413            );
19414        }
19415        multibuffer
19416    });
19417
19418    let editor = cx.add_window(|window, cx| {
19419        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19420    });
19421    cx.run_until_parked();
19422
19423    let snapshot = editor
19424        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19425        .unwrap();
19426    let hunks = snapshot
19427        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19428        .map(|hunk| match hunk {
19429            DisplayDiffHunk::Unfolded {
19430                display_row_range, ..
19431            } => display_row_range,
19432            DisplayDiffHunk::Folded { .. } => unreachable!(),
19433        })
19434        .collect::<Vec<_>>();
19435    assert_eq!(
19436        hunks,
19437        [
19438            DisplayRow(2)..DisplayRow(4),
19439            DisplayRow(7)..DisplayRow(9),
19440            DisplayRow(12)..DisplayRow(14),
19441        ]
19442    );
19443}
19444
19445#[gpui::test]
19446async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19447    init_test(cx, |_| {});
19448
19449    let mut cx = EditorTestContext::new(cx).await;
19450    cx.set_head_text(indoc! { "
19451        one
19452        two
19453        three
19454        four
19455        five
19456        "
19457    });
19458    cx.set_index_text(indoc! { "
19459        one
19460        two
19461        three
19462        four
19463        five
19464        "
19465    });
19466    cx.set_state(indoc! {"
19467        one
19468        TWO
19469        ˇTHREE
19470        FOUR
19471        five
19472    "});
19473    cx.run_until_parked();
19474    cx.update_editor(|editor, window, cx| {
19475        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19476    });
19477    cx.run_until_parked();
19478    cx.assert_index_text(Some(indoc! {"
19479        one
19480        TWO
19481        THREE
19482        FOUR
19483        five
19484    "}));
19485    cx.set_state(indoc! { "
19486        one
19487        TWO
19488        ˇTHREE-HUNDRED
19489        FOUR
19490        five
19491    "});
19492    cx.run_until_parked();
19493    cx.update_editor(|editor, window, cx| {
19494        let snapshot = editor.snapshot(window, cx);
19495        let hunks = editor
19496            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19497            .collect::<Vec<_>>();
19498        assert_eq!(hunks.len(), 1);
19499        assert_eq!(
19500            hunks[0].status(),
19501            DiffHunkStatus {
19502                kind: DiffHunkStatusKind::Modified,
19503                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19504            }
19505        );
19506
19507        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19508    });
19509    cx.run_until_parked();
19510    cx.assert_index_text(Some(indoc! {"
19511        one
19512        TWO
19513        THREE-HUNDRED
19514        FOUR
19515        five
19516    "}));
19517}
19518
19519#[gpui::test]
19520fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19521    init_test(cx, |_| {});
19522
19523    let editor = cx.add_window(|window, cx| {
19524        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19525        build_editor(buffer, window, cx)
19526    });
19527
19528    let render_args = Arc::new(Mutex::new(None));
19529    let snapshot = editor
19530        .update(cx, |editor, window, cx| {
19531            let snapshot = editor.buffer().read(cx).snapshot(cx);
19532            let range =
19533                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19534
19535            struct RenderArgs {
19536                row: MultiBufferRow,
19537                folded: bool,
19538                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19539            }
19540
19541            let crease = Crease::inline(
19542                range,
19543                FoldPlaceholder::test(),
19544                {
19545                    let toggle_callback = render_args.clone();
19546                    move |row, folded, callback, _window, _cx| {
19547                        *toggle_callback.lock() = Some(RenderArgs {
19548                            row,
19549                            folded,
19550                            callback,
19551                        });
19552                        div()
19553                    }
19554                },
19555                |_row, _folded, _window, _cx| div(),
19556            );
19557
19558            editor.insert_creases(Some(crease), cx);
19559            let snapshot = editor.snapshot(window, cx);
19560            let _div = snapshot.render_crease_toggle(
19561                MultiBufferRow(1),
19562                false,
19563                cx.entity().clone(),
19564                window,
19565                cx,
19566            );
19567            snapshot
19568        })
19569        .unwrap();
19570
19571    let render_args = render_args.lock().take().unwrap();
19572    assert_eq!(render_args.row, MultiBufferRow(1));
19573    assert!(!render_args.folded);
19574    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19575
19576    cx.update_window(*editor, |_, window, cx| {
19577        (render_args.callback)(true, window, cx)
19578    })
19579    .unwrap();
19580    let snapshot = editor
19581        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19582        .unwrap();
19583    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19584
19585    cx.update_window(*editor, |_, window, cx| {
19586        (render_args.callback)(false, window, cx)
19587    })
19588    .unwrap();
19589    let snapshot = editor
19590        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19591        .unwrap();
19592    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19593}
19594
19595#[gpui::test]
19596async fn test_input_text(cx: &mut TestAppContext) {
19597    init_test(cx, |_| {});
19598    let mut cx = EditorTestContext::new(cx).await;
19599
19600    cx.set_state(
19601        &r#"ˇone
19602        two
19603
19604        three
19605        fourˇ
19606        five
19607
19608        siˇx"#
19609            .unindent(),
19610    );
19611
19612    cx.dispatch_action(HandleInput(String::new()));
19613    cx.assert_editor_state(
19614        &r#"ˇone
19615        two
19616
19617        three
19618        fourˇ
19619        five
19620
19621        siˇx"#
19622            .unindent(),
19623    );
19624
19625    cx.dispatch_action(HandleInput("AAAA".to_string()));
19626    cx.assert_editor_state(
19627        &r#"AAAAˇone
19628        two
19629
19630        three
19631        fourAAAAˇ
19632        five
19633
19634        siAAAAˇx"#
19635            .unindent(),
19636    );
19637}
19638
19639#[gpui::test]
19640async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19641    init_test(cx, |_| {});
19642
19643    let mut cx = EditorTestContext::new(cx).await;
19644    cx.set_state(
19645        r#"let foo = 1;
19646let foo = 2;
19647let foo = 3;
19648let fooˇ = 4;
19649let foo = 5;
19650let foo = 6;
19651let foo = 7;
19652let foo = 8;
19653let foo = 9;
19654let foo = 10;
19655let foo = 11;
19656let foo = 12;
19657let foo = 13;
19658let foo = 14;
19659let foo = 15;"#,
19660    );
19661
19662    cx.update_editor(|e, window, cx| {
19663        assert_eq!(
19664            e.next_scroll_position,
19665            NextScrollCursorCenterTopBottom::Center,
19666            "Default next scroll direction is center",
19667        );
19668
19669        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19670        assert_eq!(
19671            e.next_scroll_position,
19672            NextScrollCursorCenterTopBottom::Top,
19673            "After center, next scroll direction should be top",
19674        );
19675
19676        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19677        assert_eq!(
19678            e.next_scroll_position,
19679            NextScrollCursorCenterTopBottom::Bottom,
19680            "After top, next scroll direction should be bottom",
19681        );
19682
19683        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19684        assert_eq!(
19685            e.next_scroll_position,
19686            NextScrollCursorCenterTopBottom::Center,
19687            "After bottom, scrolling should start over",
19688        );
19689
19690        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19691        assert_eq!(
19692            e.next_scroll_position,
19693            NextScrollCursorCenterTopBottom::Top,
19694            "Scrolling continues if retriggered fast enough"
19695        );
19696    });
19697
19698    cx.executor()
19699        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19700    cx.executor().run_until_parked();
19701    cx.update_editor(|e, _, _| {
19702        assert_eq!(
19703            e.next_scroll_position,
19704            NextScrollCursorCenterTopBottom::Center,
19705            "If scrolling is not triggered fast enough, it should reset"
19706        );
19707    });
19708}
19709
19710#[gpui::test]
19711async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19712    init_test(cx, |_| {});
19713    let mut cx = EditorLspTestContext::new_rust(
19714        lsp::ServerCapabilities {
19715            definition_provider: Some(lsp::OneOf::Left(true)),
19716            references_provider: Some(lsp::OneOf::Left(true)),
19717            ..lsp::ServerCapabilities::default()
19718        },
19719        cx,
19720    )
19721    .await;
19722
19723    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19724        let go_to_definition = cx
19725            .lsp
19726            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19727                move |params, _| async move {
19728                    if empty_go_to_definition {
19729                        Ok(None)
19730                    } else {
19731                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19732                            uri: params.text_document_position_params.text_document.uri,
19733                            range: lsp::Range::new(
19734                                lsp::Position::new(4, 3),
19735                                lsp::Position::new(4, 6),
19736                            ),
19737                        })))
19738                    }
19739                },
19740            );
19741        let references = cx
19742            .lsp
19743            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19744                Ok(Some(vec![lsp::Location {
19745                    uri: params.text_document_position.text_document.uri,
19746                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19747                }]))
19748            });
19749        (go_to_definition, references)
19750    };
19751
19752    cx.set_state(
19753        &r#"fn one() {
19754            let mut a = ˇtwo();
19755        }
19756
19757        fn two() {}"#
19758            .unindent(),
19759    );
19760    set_up_lsp_handlers(false, &mut cx);
19761    let navigated = cx
19762        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19763        .await
19764        .expect("Failed to navigate to definition");
19765    assert_eq!(
19766        navigated,
19767        Navigated::Yes,
19768        "Should have navigated to definition from the GetDefinition response"
19769    );
19770    cx.assert_editor_state(
19771        &r#"fn one() {
19772            let mut a = two();
19773        }
19774
19775        fn «twoˇ»() {}"#
19776            .unindent(),
19777    );
19778
19779    let editors = cx.update_workspace(|workspace, _, cx| {
19780        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19781    });
19782    cx.update_editor(|_, _, test_editor_cx| {
19783        assert_eq!(
19784            editors.len(),
19785            1,
19786            "Initially, only one, test, editor should be open in the workspace"
19787        );
19788        assert_eq!(
19789            test_editor_cx.entity(),
19790            editors.last().expect("Asserted len is 1").clone()
19791        );
19792    });
19793
19794    set_up_lsp_handlers(true, &mut cx);
19795    let navigated = cx
19796        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19797        .await
19798        .expect("Failed to navigate to lookup references");
19799    assert_eq!(
19800        navigated,
19801        Navigated::Yes,
19802        "Should have navigated to references as a fallback after empty GoToDefinition response"
19803    );
19804    // We should not change the selections in the existing file,
19805    // if opening another milti buffer with the references
19806    cx.assert_editor_state(
19807        &r#"fn one() {
19808            let mut a = two();
19809        }
19810
19811        fn «twoˇ»() {}"#
19812            .unindent(),
19813    );
19814    let editors = cx.update_workspace(|workspace, _, cx| {
19815        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19816    });
19817    cx.update_editor(|_, _, test_editor_cx| {
19818        assert_eq!(
19819            editors.len(),
19820            2,
19821            "After falling back to references search, we open a new editor with the results"
19822        );
19823        let references_fallback_text = editors
19824            .into_iter()
19825            .find(|new_editor| *new_editor != test_editor_cx.entity())
19826            .expect("Should have one non-test editor now")
19827            .read(test_editor_cx)
19828            .text(test_editor_cx);
19829        assert_eq!(
19830            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19831            "Should use the range from the references response and not the GoToDefinition one"
19832        );
19833    });
19834}
19835
19836#[gpui::test]
19837async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19838    init_test(cx, |_| {});
19839    cx.update(|cx| {
19840        let mut editor_settings = EditorSettings::get_global(cx).clone();
19841        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19842        EditorSettings::override_global(editor_settings, cx);
19843    });
19844    let mut cx = EditorLspTestContext::new_rust(
19845        lsp::ServerCapabilities {
19846            definition_provider: Some(lsp::OneOf::Left(true)),
19847            references_provider: Some(lsp::OneOf::Left(true)),
19848            ..lsp::ServerCapabilities::default()
19849        },
19850        cx,
19851    )
19852    .await;
19853    let original_state = r#"fn one() {
19854        let mut a = ˇtwo();
19855    }
19856
19857    fn two() {}"#
19858        .unindent();
19859    cx.set_state(&original_state);
19860
19861    let mut go_to_definition = cx
19862        .lsp
19863        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19864            move |_, _| async move { Ok(None) },
19865        );
19866    let _references = cx
19867        .lsp
19868        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19869            panic!("Should not call for references with no go to definition fallback")
19870        });
19871
19872    let navigated = cx
19873        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19874        .await
19875        .expect("Failed to navigate to lookup references");
19876    go_to_definition
19877        .next()
19878        .await
19879        .expect("Should have called the go_to_definition handler");
19880
19881    assert_eq!(
19882        navigated,
19883        Navigated::No,
19884        "Should have navigated to references as a fallback after empty GoToDefinition response"
19885    );
19886    cx.assert_editor_state(&original_state);
19887    let editors = cx.update_workspace(|workspace, _, cx| {
19888        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19889    });
19890    cx.update_editor(|_, _, _| {
19891        assert_eq!(
19892            editors.len(),
19893            1,
19894            "After unsuccessful fallback, no other editor should have been opened"
19895        );
19896    });
19897}
19898
19899#[gpui::test]
19900async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19901    init_test(cx, |_| {});
19902
19903    let language = Arc::new(Language::new(
19904        LanguageConfig::default(),
19905        Some(tree_sitter_rust::LANGUAGE.into()),
19906    ));
19907
19908    let text = r#"
19909        #[cfg(test)]
19910        mod tests() {
19911            #[test]
19912            fn runnable_1() {
19913                let a = 1;
19914            }
19915
19916            #[test]
19917            fn runnable_2() {
19918                let a = 1;
19919                let b = 2;
19920            }
19921        }
19922    "#
19923    .unindent();
19924
19925    let fs = FakeFs::new(cx.executor());
19926    fs.insert_file("/file.rs", Default::default()).await;
19927
19928    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19929    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19930    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19931    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19932    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19933
19934    let editor = cx.new_window_entity(|window, cx| {
19935        Editor::new(
19936            EditorMode::full(),
19937            multi_buffer,
19938            Some(project.clone()),
19939            window,
19940            cx,
19941        )
19942    });
19943
19944    editor.update_in(cx, |editor, window, cx| {
19945        let snapshot = editor.buffer().read(cx).snapshot(cx);
19946        editor.tasks.insert(
19947            (buffer.read(cx).remote_id(), 3),
19948            RunnableTasks {
19949                templates: vec![],
19950                offset: snapshot.anchor_before(43),
19951                column: 0,
19952                extra_variables: HashMap::default(),
19953                context_range: BufferOffset(43)..BufferOffset(85),
19954            },
19955        );
19956        editor.tasks.insert(
19957            (buffer.read(cx).remote_id(), 8),
19958            RunnableTasks {
19959                templates: vec![],
19960                offset: snapshot.anchor_before(86),
19961                column: 0,
19962                extra_variables: HashMap::default(),
19963                context_range: BufferOffset(86)..BufferOffset(191),
19964            },
19965        );
19966
19967        // Test finding task when cursor is inside function body
19968        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19969            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19970        });
19971        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19972        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19973
19974        // Test finding task when cursor is on function name
19975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19976            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19977        });
19978        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19979        assert_eq!(row, 8, "Should find task when cursor is on function name");
19980    });
19981}
19982
19983#[gpui::test]
19984async fn test_folding_buffers(cx: &mut TestAppContext) {
19985    init_test(cx, |_| {});
19986
19987    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19988    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19989    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19990
19991    let fs = FakeFs::new(cx.executor());
19992    fs.insert_tree(
19993        path!("/a"),
19994        json!({
19995            "first.rs": sample_text_1,
19996            "second.rs": sample_text_2,
19997            "third.rs": sample_text_3,
19998        }),
19999    )
20000    .await;
20001    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20002    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20003    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20004    let worktree = project.update(cx, |project, cx| {
20005        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20006        assert_eq!(worktrees.len(), 1);
20007        worktrees.pop().unwrap()
20008    });
20009    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20010
20011    let buffer_1 = project
20012        .update(cx, |project, cx| {
20013            project.open_buffer((worktree_id, "first.rs"), cx)
20014        })
20015        .await
20016        .unwrap();
20017    let buffer_2 = project
20018        .update(cx, |project, cx| {
20019            project.open_buffer((worktree_id, "second.rs"), cx)
20020        })
20021        .await
20022        .unwrap();
20023    let buffer_3 = project
20024        .update(cx, |project, cx| {
20025            project.open_buffer((worktree_id, "third.rs"), cx)
20026        })
20027        .await
20028        .unwrap();
20029
20030    let multi_buffer = cx.new(|cx| {
20031        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20032        multi_buffer.push_excerpts(
20033            buffer_1.clone(),
20034            [
20035                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20036                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20037                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20038            ],
20039            cx,
20040        );
20041        multi_buffer.push_excerpts(
20042            buffer_2.clone(),
20043            [
20044                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20045                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20046                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20047            ],
20048            cx,
20049        );
20050        multi_buffer.push_excerpts(
20051            buffer_3.clone(),
20052            [
20053                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20054                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20055                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20056            ],
20057            cx,
20058        );
20059        multi_buffer
20060    });
20061    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20062        Editor::new(
20063            EditorMode::full(),
20064            multi_buffer.clone(),
20065            Some(project.clone()),
20066            window,
20067            cx,
20068        )
20069    });
20070
20071    assert_eq!(
20072        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20073        "\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",
20074    );
20075
20076    multi_buffer_editor.update(cx, |editor, cx| {
20077        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20078    });
20079    assert_eq!(
20080        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20081        "\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",
20082        "After folding the first buffer, its text should not be displayed"
20083    );
20084
20085    multi_buffer_editor.update(cx, |editor, cx| {
20086        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20087    });
20088    assert_eq!(
20089        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20090        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20091        "After folding the second buffer, its text should not be displayed"
20092    );
20093
20094    multi_buffer_editor.update(cx, |editor, cx| {
20095        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20096    });
20097    assert_eq!(
20098        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20099        "\n\n\n\n\n",
20100        "After folding the third buffer, its text should not be displayed"
20101    );
20102
20103    // Emulate selection inside the fold logic, that should work
20104    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20105        editor
20106            .snapshot(window, cx)
20107            .next_line_boundary(Point::new(0, 4));
20108    });
20109
20110    multi_buffer_editor.update(cx, |editor, cx| {
20111        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20112    });
20113    assert_eq!(
20114        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20115        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20116        "After unfolding the second buffer, its text should be displayed"
20117    );
20118
20119    // Typing inside of buffer 1 causes that buffer to be unfolded.
20120    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20121        assert_eq!(
20122            multi_buffer
20123                .read(cx)
20124                .snapshot(cx)
20125                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20126                .collect::<String>(),
20127            "bbbb"
20128        );
20129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20130            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20131        });
20132        editor.handle_input("B", window, cx);
20133    });
20134
20135    assert_eq!(
20136        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20137        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20138        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20139    );
20140
20141    multi_buffer_editor.update(cx, |editor, cx| {
20142        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20143    });
20144    assert_eq!(
20145        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20146        "\n\nB\n\n\n\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",
20147        "After unfolding the all buffers, all original text should be displayed"
20148    );
20149}
20150
20151#[gpui::test]
20152async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20153    init_test(cx, |_| {});
20154
20155    let sample_text_1 = "1111\n2222\n3333".to_string();
20156    let sample_text_2 = "4444\n5555\n6666".to_string();
20157    let sample_text_3 = "7777\n8888\n9999".to_string();
20158
20159    let fs = FakeFs::new(cx.executor());
20160    fs.insert_tree(
20161        path!("/a"),
20162        json!({
20163            "first.rs": sample_text_1,
20164            "second.rs": sample_text_2,
20165            "third.rs": sample_text_3,
20166        }),
20167    )
20168    .await;
20169    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20170    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20171    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20172    let worktree = project.update(cx, |project, cx| {
20173        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20174        assert_eq!(worktrees.len(), 1);
20175        worktrees.pop().unwrap()
20176    });
20177    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20178
20179    let buffer_1 = project
20180        .update(cx, |project, cx| {
20181            project.open_buffer((worktree_id, "first.rs"), cx)
20182        })
20183        .await
20184        .unwrap();
20185    let buffer_2 = project
20186        .update(cx, |project, cx| {
20187            project.open_buffer((worktree_id, "second.rs"), cx)
20188        })
20189        .await
20190        .unwrap();
20191    let buffer_3 = project
20192        .update(cx, |project, cx| {
20193            project.open_buffer((worktree_id, "third.rs"), cx)
20194        })
20195        .await
20196        .unwrap();
20197
20198    let multi_buffer = cx.new(|cx| {
20199        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20200        multi_buffer.push_excerpts(
20201            buffer_1.clone(),
20202            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20203            cx,
20204        );
20205        multi_buffer.push_excerpts(
20206            buffer_2.clone(),
20207            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20208            cx,
20209        );
20210        multi_buffer.push_excerpts(
20211            buffer_3.clone(),
20212            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20213            cx,
20214        );
20215        multi_buffer
20216    });
20217
20218    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20219        Editor::new(
20220            EditorMode::full(),
20221            multi_buffer,
20222            Some(project.clone()),
20223            window,
20224            cx,
20225        )
20226    });
20227
20228    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20229    assert_eq!(
20230        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20231        full_text,
20232    );
20233
20234    multi_buffer_editor.update(cx, |editor, cx| {
20235        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20236    });
20237    assert_eq!(
20238        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20239        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20240        "After folding the first buffer, its text should not be displayed"
20241    );
20242
20243    multi_buffer_editor.update(cx, |editor, cx| {
20244        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20245    });
20246
20247    assert_eq!(
20248        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20249        "\n\n\n\n\n\n7777\n8888\n9999",
20250        "After folding the second buffer, its text should not be displayed"
20251    );
20252
20253    multi_buffer_editor.update(cx, |editor, cx| {
20254        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20255    });
20256    assert_eq!(
20257        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20258        "\n\n\n\n\n",
20259        "After folding the third buffer, its text should not be displayed"
20260    );
20261
20262    multi_buffer_editor.update(cx, |editor, cx| {
20263        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20264    });
20265    assert_eq!(
20266        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20267        "\n\n\n\n4444\n5555\n6666\n\n",
20268        "After unfolding the second buffer, its text should be displayed"
20269    );
20270
20271    multi_buffer_editor.update(cx, |editor, cx| {
20272        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20273    });
20274    assert_eq!(
20275        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20276        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20277        "After unfolding the first buffer, its text should be displayed"
20278    );
20279
20280    multi_buffer_editor.update(cx, |editor, cx| {
20281        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20282    });
20283    assert_eq!(
20284        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20285        full_text,
20286        "After unfolding all buffers, all original text should be displayed"
20287    );
20288}
20289
20290#[gpui::test]
20291async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20292    init_test(cx, |_| {});
20293
20294    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20295
20296    let fs = FakeFs::new(cx.executor());
20297    fs.insert_tree(
20298        path!("/a"),
20299        json!({
20300            "main.rs": sample_text,
20301        }),
20302    )
20303    .await;
20304    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20305    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20306    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20307    let worktree = project.update(cx, |project, cx| {
20308        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20309        assert_eq!(worktrees.len(), 1);
20310        worktrees.pop().unwrap()
20311    });
20312    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20313
20314    let buffer_1 = project
20315        .update(cx, |project, cx| {
20316            project.open_buffer((worktree_id, "main.rs"), cx)
20317        })
20318        .await
20319        .unwrap();
20320
20321    let multi_buffer = cx.new(|cx| {
20322        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20323        multi_buffer.push_excerpts(
20324            buffer_1.clone(),
20325            [ExcerptRange::new(
20326                Point::new(0, 0)
20327                    ..Point::new(
20328                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20329                        0,
20330                    ),
20331            )],
20332            cx,
20333        );
20334        multi_buffer
20335    });
20336    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20337        Editor::new(
20338            EditorMode::full(),
20339            multi_buffer,
20340            Some(project.clone()),
20341            window,
20342            cx,
20343        )
20344    });
20345
20346    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20347    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20348        enum TestHighlight {}
20349        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20350        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20351        editor.highlight_text::<TestHighlight>(
20352            vec![highlight_range.clone()],
20353            HighlightStyle::color(Hsla::green()),
20354            cx,
20355        );
20356        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20357            s.select_ranges(Some(highlight_range))
20358        });
20359    });
20360
20361    let full_text = format!("\n\n{sample_text}");
20362    assert_eq!(
20363        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20364        full_text,
20365    );
20366}
20367
20368#[gpui::test]
20369async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20370    init_test(cx, |_| {});
20371    cx.update(|cx| {
20372        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20373            "keymaps/default-linux.json",
20374            cx,
20375        )
20376        .unwrap();
20377        cx.bind_keys(default_key_bindings);
20378    });
20379
20380    let (editor, cx) = cx.add_window_view(|window, cx| {
20381        let multi_buffer = MultiBuffer::build_multi(
20382            [
20383                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20384                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20385                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20386                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20387            ],
20388            cx,
20389        );
20390        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20391
20392        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20393        // fold all but the second buffer, so that we test navigating between two
20394        // adjacent folded buffers, as well as folded buffers at the start and
20395        // end the multibuffer
20396        editor.fold_buffer(buffer_ids[0], cx);
20397        editor.fold_buffer(buffer_ids[2], cx);
20398        editor.fold_buffer(buffer_ids[3], cx);
20399
20400        editor
20401    });
20402    cx.simulate_resize(size(px(1000.), px(1000.)));
20403
20404    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20405    cx.assert_excerpts_with_selections(indoc! {"
20406        [EXCERPT]
20407        ˇ[FOLDED]
20408        [EXCERPT]
20409        a1
20410        b1
20411        [EXCERPT]
20412        [FOLDED]
20413        [EXCERPT]
20414        [FOLDED]
20415        "
20416    });
20417    cx.simulate_keystroke("down");
20418    cx.assert_excerpts_with_selections(indoc! {"
20419        [EXCERPT]
20420        [FOLDED]
20421        [EXCERPT]
20422        ˇa1
20423        b1
20424        [EXCERPT]
20425        [FOLDED]
20426        [EXCERPT]
20427        [FOLDED]
20428        "
20429    });
20430    cx.simulate_keystroke("down");
20431    cx.assert_excerpts_with_selections(indoc! {"
20432        [EXCERPT]
20433        [FOLDED]
20434        [EXCERPT]
20435        a1
20436        ˇb1
20437        [EXCERPT]
20438        [FOLDED]
20439        [EXCERPT]
20440        [FOLDED]
20441        "
20442    });
20443    cx.simulate_keystroke("down");
20444    cx.assert_excerpts_with_selections(indoc! {"
20445        [EXCERPT]
20446        [FOLDED]
20447        [EXCERPT]
20448        a1
20449        b1
20450        ˇ[EXCERPT]
20451        [FOLDED]
20452        [EXCERPT]
20453        [FOLDED]
20454        "
20455    });
20456    cx.simulate_keystroke("down");
20457    cx.assert_excerpts_with_selections(indoc! {"
20458        [EXCERPT]
20459        [FOLDED]
20460        [EXCERPT]
20461        a1
20462        b1
20463        [EXCERPT]
20464        ˇ[FOLDED]
20465        [EXCERPT]
20466        [FOLDED]
20467        "
20468    });
20469    for _ in 0..5 {
20470        cx.simulate_keystroke("down");
20471        cx.assert_excerpts_with_selections(indoc! {"
20472            [EXCERPT]
20473            [FOLDED]
20474            [EXCERPT]
20475            a1
20476            b1
20477            [EXCERPT]
20478            [FOLDED]
20479            [EXCERPT]
20480            ˇ[FOLDED]
20481            "
20482        });
20483    }
20484
20485    cx.simulate_keystroke("up");
20486    cx.assert_excerpts_with_selections(indoc! {"
20487        [EXCERPT]
20488        [FOLDED]
20489        [EXCERPT]
20490        a1
20491        b1
20492        [EXCERPT]
20493        ˇ[FOLDED]
20494        [EXCERPT]
20495        [FOLDED]
20496        "
20497    });
20498    cx.simulate_keystroke("up");
20499    cx.assert_excerpts_with_selections(indoc! {"
20500        [EXCERPT]
20501        [FOLDED]
20502        [EXCERPT]
20503        a1
20504        b1
20505        ˇ[EXCERPT]
20506        [FOLDED]
20507        [EXCERPT]
20508        [FOLDED]
20509        "
20510    });
20511    cx.simulate_keystroke("up");
20512    cx.assert_excerpts_with_selections(indoc! {"
20513        [EXCERPT]
20514        [FOLDED]
20515        [EXCERPT]
20516        a1
20517        ˇb1
20518        [EXCERPT]
20519        [FOLDED]
20520        [EXCERPT]
20521        [FOLDED]
20522        "
20523    });
20524    cx.simulate_keystroke("up");
20525    cx.assert_excerpts_with_selections(indoc! {"
20526        [EXCERPT]
20527        [FOLDED]
20528        [EXCERPT]
20529        ˇa1
20530        b1
20531        [EXCERPT]
20532        [FOLDED]
20533        [EXCERPT]
20534        [FOLDED]
20535        "
20536    });
20537    for _ in 0..5 {
20538        cx.simulate_keystroke("up");
20539        cx.assert_excerpts_with_selections(indoc! {"
20540            [EXCERPT]
20541            ˇ[FOLDED]
20542            [EXCERPT]
20543            a1
20544            b1
20545            [EXCERPT]
20546            [FOLDED]
20547            [EXCERPT]
20548            [FOLDED]
20549            "
20550        });
20551    }
20552}
20553
20554#[gpui::test]
20555async fn test_inline_completion_text(cx: &mut TestAppContext) {
20556    init_test(cx, |_| {});
20557
20558    // Simple insertion
20559    assert_highlighted_edits(
20560        "Hello, world!",
20561        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20562        true,
20563        cx,
20564        |highlighted_edits, cx| {
20565            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20566            assert_eq!(highlighted_edits.highlights.len(), 1);
20567            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20568            assert_eq!(
20569                highlighted_edits.highlights[0].1.background_color,
20570                Some(cx.theme().status().created_background)
20571            );
20572        },
20573    )
20574    .await;
20575
20576    // Replacement
20577    assert_highlighted_edits(
20578        "This is a test.",
20579        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20580        false,
20581        cx,
20582        |highlighted_edits, cx| {
20583            assert_eq!(highlighted_edits.text, "That is a test.");
20584            assert_eq!(highlighted_edits.highlights.len(), 1);
20585            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20586            assert_eq!(
20587                highlighted_edits.highlights[0].1.background_color,
20588                Some(cx.theme().status().created_background)
20589            );
20590        },
20591    )
20592    .await;
20593
20594    // Multiple edits
20595    assert_highlighted_edits(
20596        "Hello, world!",
20597        vec![
20598            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20599            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20600        ],
20601        false,
20602        cx,
20603        |highlighted_edits, cx| {
20604            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20605            assert_eq!(highlighted_edits.highlights.len(), 2);
20606            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20607            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20608            assert_eq!(
20609                highlighted_edits.highlights[0].1.background_color,
20610                Some(cx.theme().status().created_background)
20611            );
20612            assert_eq!(
20613                highlighted_edits.highlights[1].1.background_color,
20614                Some(cx.theme().status().created_background)
20615            );
20616        },
20617    )
20618    .await;
20619
20620    // Multiple lines with edits
20621    assert_highlighted_edits(
20622        "First line\nSecond line\nThird line\nFourth line",
20623        vec![
20624            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20625            (
20626                Point::new(2, 0)..Point::new(2, 10),
20627                "New third line".to_string(),
20628            ),
20629            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20630        ],
20631        false,
20632        cx,
20633        |highlighted_edits, cx| {
20634            assert_eq!(
20635                highlighted_edits.text,
20636                "Second modified\nNew third line\nFourth updated line"
20637            );
20638            assert_eq!(highlighted_edits.highlights.len(), 3);
20639            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20640            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20641            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20642            for highlight in &highlighted_edits.highlights {
20643                assert_eq!(
20644                    highlight.1.background_color,
20645                    Some(cx.theme().status().created_background)
20646                );
20647            }
20648        },
20649    )
20650    .await;
20651}
20652
20653#[gpui::test]
20654async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20655    init_test(cx, |_| {});
20656
20657    // Deletion
20658    assert_highlighted_edits(
20659        "Hello, world!",
20660        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20661        true,
20662        cx,
20663        |highlighted_edits, cx| {
20664            assert_eq!(highlighted_edits.text, "Hello, world!");
20665            assert_eq!(highlighted_edits.highlights.len(), 1);
20666            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20667            assert_eq!(
20668                highlighted_edits.highlights[0].1.background_color,
20669                Some(cx.theme().status().deleted_background)
20670            );
20671        },
20672    )
20673    .await;
20674
20675    // Insertion
20676    assert_highlighted_edits(
20677        "Hello, world!",
20678        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20679        true,
20680        cx,
20681        |highlighted_edits, cx| {
20682            assert_eq!(highlighted_edits.highlights.len(), 1);
20683            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20684            assert_eq!(
20685                highlighted_edits.highlights[0].1.background_color,
20686                Some(cx.theme().status().created_background)
20687            );
20688        },
20689    )
20690    .await;
20691}
20692
20693async fn assert_highlighted_edits(
20694    text: &str,
20695    edits: Vec<(Range<Point>, String)>,
20696    include_deletions: bool,
20697    cx: &mut TestAppContext,
20698    assertion_fn: impl Fn(HighlightedText, &App),
20699) {
20700    let window = cx.add_window(|window, cx| {
20701        let buffer = MultiBuffer::build_simple(text, cx);
20702        Editor::new(EditorMode::full(), buffer, None, window, cx)
20703    });
20704    let cx = &mut VisualTestContext::from_window(*window, cx);
20705
20706    let (buffer, snapshot) = window
20707        .update(cx, |editor, _window, cx| {
20708            (
20709                editor.buffer().clone(),
20710                editor.buffer().read(cx).snapshot(cx),
20711            )
20712        })
20713        .unwrap();
20714
20715    let edits = edits
20716        .into_iter()
20717        .map(|(range, edit)| {
20718            (
20719                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20720                edit,
20721            )
20722        })
20723        .collect::<Vec<_>>();
20724
20725    let text_anchor_edits = edits
20726        .clone()
20727        .into_iter()
20728        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20729        .collect::<Vec<_>>();
20730
20731    let edit_preview = window
20732        .update(cx, |_, _window, cx| {
20733            buffer
20734                .read(cx)
20735                .as_singleton()
20736                .unwrap()
20737                .read(cx)
20738                .preview_edits(text_anchor_edits.into(), cx)
20739        })
20740        .unwrap()
20741        .await;
20742
20743    cx.update(|_window, cx| {
20744        let highlighted_edits = inline_completion_edit_text(
20745            &snapshot.as_singleton().unwrap().2,
20746            &edits,
20747            &edit_preview,
20748            include_deletions,
20749            cx,
20750        );
20751        assertion_fn(highlighted_edits, cx)
20752    });
20753}
20754
20755#[track_caller]
20756fn assert_breakpoint(
20757    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20758    path: &Arc<Path>,
20759    expected: Vec<(u32, Breakpoint)>,
20760) {
20761    if expected.len() == 0usize {
20762        assert!(!breakpoints.contains_key(path), "{}", path.display());
20763    } else {
20764        let mut breakpoint = breakpoints
20765            .get(path)
20766            .unwrap()
20767            .into_iter()
20768            .map(|breakpoint| {
20769                (
20770                    breakpoint.row,
20771                    Breakpoint {
20772                        message: breakpoint.message.clone(),
20773                        state: breakpoint.state,
20774                        condition: breakpoint.condition.clone(),
20775                        hit_condition: breakpoint.hit_condition.clone(),
20776                    },
20777                )
20778            })
20779            .collect::<Vec<_>>();
20780
20781        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20782
20783        assert_eq!(expected, breakpoint);
20784    }
20785}
20786
20787fn add_log_breakpoint_at_cursor(
20788    editor: &mut Editor,
20789    log_message: &str,
20790    window: &mut Window,
20791    cx: &mut Context<Editor>,
20792) {
20793    let (anchor, bp) = editor
20794        .breakpoints_at_cursors(window, cx)
20795        .first()
20796        .and_then(|(anchor, bp)| {
20797            if let Some(bp) = bp {
20798                Some((*anchor, bp.clone()))
20799            } else {
20800                None
20801            }
20802        })
20803        .unwrap_or_else(|| {
20804            let cursor_position: Point = editor.selections.newest(cx).head();
20805
20806            let breakpoint_position = editor
20807                .snapshot(window, cx)
20808                .display_snapshot
20809                .buffer_snapshot
20810                .anchor_before(Point::new(cursor_position.row, 0));
20811
20812            (breakpoint_position, Breakpoint::new_log(&log_message))
20813        });
20814
20815    editor.edit_breakpoint_at_anchor(
20816        anchor,
20817        bp,
20818        BreakpointEditAction::EditLogMessage(log_message.into()),
20819        cx,
20820    );
20821}
20822
20823#[gpui::test]
20824async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20825    init_test(cx, |_| {});
20826
20827    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20828    let fs = FakeFs::new(cx.executor());
20829    fs.insert_tree(
20830        path!("/a"),
20831        json!({
20832            "main.rs": sample_text,
20833        }),
20834    )
20835    .await;
20836    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20837    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20838    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20839
20840    let fs = FakeFs::new(cx.executor());
20841    fs.insert_tree(
20842        path!("/a"),
20843        json!({
20844            "main.rs": sample_text,
20845        }),
20846    )
20847    .await;
20848    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20849    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20850    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20851    let worktree_id = workspace
20852        .update(cx, |workspace, _window, cx| {
20853            workspace.project().update(cx, |project, cx| {
20854                project.worktrees(cx).next().unwrap().read(cx).id()
20855            })
20856        })
20857        .unwrap();
20858
20859    let buffer = project
20860        .update(cx, |project, cx| {
20861            project.open_buffer((worktree_id, "main.rs"), cx)
20862        })
20863        .await
20864        .unwrap();
20865
20866    let (editor, cx) = cx.add_window_view(|window, cx| {
20867        Editor::new(
20868            EditorMode::full(),
20869            MultiBuffer::build_from_buffer(buffer, cx),
20870            Some(project.clone()),
20871            window,
20872            cx,
20873        )
20874    });
20875
20876    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20877    let abs_path = project.read_with(cx, |project, cx| {
20878        project
20879            .absolute_path(&project_path, cx)
20880            .map(|path_buf| Arc::from(path_buf.to_owned()))
20881            .unwrap()
20882    });
20883
20884    // assert we can add breakpoint on the first line
20885    editor.update_in(cx, |editor, window, cx| {
20886        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20887        editor.move_to_end(&MoveToEnd, window, cx);
20888        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20889    });
20890
20891    let breakpoints = editor.update(cx, |editor, cx| {
20892        editor
20893            .breakpoint_store()
20894            .as_ref()
20895            .unwrap()
20896            .read(cx)
20897            .all_source_breakpoints(cx)
20898            .clone()
20899    });
20900
20901    assert_eq!(1, breakpoints.len());
20902    assert_breakpoint(
20903        &breakpoints,
20904        &abs_path,
20905        vec![
20906            (0, Breakpoint::new_standard()),
20907            (3, Breakpoint::new_standard()),
20908        ],
20909    );
20910
20911    editor.update_in(cx, |editor, window, cx| {
20912        editor.move_to_beginning(&MoveToBeginning, window, cx);
20913        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20914    });
20915
20916    let breakpoints = editor.update(cx, |editor, cx| {
20917        editor
20918            .breakpoint_store()
20919            .as_ref()
20920            .unwrap()
20921            .read(cx)
20922            .all_source_breakpoints(cx)
20923            .clone()
20924    });
20925
20926    assert_eq!(1, breakpoints.len());
20927    assert_breakpoint(
20928        &breakpoints,
20929        &abs_path,
20930        vec![(3, Breakpoint::new_standard())],
20931    );
20932
20933    editor.update_in(cx, |editor, window, cx| {
20934        editor.move_to_end(&MoveToEnd, window, cx);
20935        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20936    });
20937
20938    let breakpoints = editor.update(cx, |editor, cx| {
20939        editor
20940            .breakpoint_store()
20941            .as_ref()
20942            .unwrap()
20943            .read(cx)
20944            .all_source_breakpoints(cx)
20945            .clone()
20946    });
20947
20948    assert_eq!(0, breakpoints.len());
20949    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20950}
20951
20952#[gpui::test]
20953async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20954    init_test(cx, |_| {});
20955
20956    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20957
20958    let fs = FakeFs::new(cx.executor());
20959    fs.insert_tree(
20960        path!("/a"),
20961        json!({
20962            "main.rs": sample_text,
20963        }),
20964    )
20965    .await;
20966    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20967    let (workspace, cx) =
20968        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20969
20970    let worktree_id = workspace.update(cx, |workspace, cx| {
20971        workspace.project().update(cx, |project, cx| {
20972            project.worktrees(cx).next().unwrap().read(cx).id()
20973        })
20974    });
20975
20976    let buffer = project
20977        .update(cx, |project, cx| {
20978            project.open_buffer((worktree_id, "main.rs"), cx)
20979        })
20980        .await
20981        .unwrap();
20982
20983    let (editor, cx) = cx.add_window_view(|window, cx| {
20984        Editor::new(
20985            EditorMode::full(),
20986            MultiBuffer::build_from_buffer(buffer, cx),
20987            Some(project.clone()),
20988            window,
20989            cx,
20990        )
20991    });
20992
20993    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20994    let abs_path = project.read_with(cx, |project, cx| {
20995        project
20996            .absolute_path(&project_path, cx)
20997            .map(|path_buf| Arc::from(path_buf.to_owned()))
20998            .unwrap()
20999    });
21000
21001    editor.update_in(cx, |editor, window, cx| {
21002        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21003    });
21004
21005    let breakpoints = editor.update(cx, |editor, cx| {
21006        editor
21007            .breakpoint_store()
21008            .as_ref()
21009            .unwrap()
21010            .read(cx)
21011            .all_source_breakpoints(cx)
21012            .clone()
21013    });
21014
21015    assert_breakpoint(
21016        &breakpoints,
21017        &abs_path,
21018        vec![(0, Breakpoint::new_log("hello world"))],
21019    );
21020
21021    // Removing a log message from a log breakpoint should remove it
21022    editor.update_in(cx, |editor, window, cx| {
21023        add_log_breakpoint_at_cursor(editor, "", window, cx);
21024    });
21025
21026    let breakpoints = editor.update(cx, |editor, cx| {
21027        editor
21028            .breakpoint_store()
21029            .as_ref()
21030            .unwrap()
21031            .read(cx)
21032            .all_source_breakpoints(cx)
21033            .clone()
21034    });
21035
21036    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21037
21038    editor.update_in(cx, |editor, window, cx| {
21039        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21040        editor.move_to_end(&MoveToEnd, window, cx);
21041        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21042        // Not adding a log message to a standard breakpoint shouldn't remove it
21043        add_log_breakpoint_at_cursor(editor, "", window, cx);
21044    });
21045
21046    let breakpoints = editor.update(cx, |editor, cx| {
21047        editor
21048            .breakpoint_store()
21049            .as_ref()
21050            .unwrap()
21051            .read(cx)
21052            .all_source_breakpoints(cx)
21053            .clone()
21054    });
21055
21056    assert_breakpoint(
21057        &breakpoints,
21058        &abs_path,
21059        vec![
21060            (0, Breakpoint::new_standard()),
21061            (3, Breakpoint::new_standard()),
21062        ],
21063    );
21064
21065    editor.update_in(cx, |editor, window, cx| {
21066        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21067    });
21068
21069    let breakpoints = editor.update(cx, |editor, cx| {
21070        editor
21071            .breakpoint_store()
21072            .as_ref()
21073            .unwrap()
21074            .read(cx)
21075            .all_source_breakpoints(cx)
21076            .clone()
21077    });
21078
21079    assert_breakpoint(
21080        &breakpoints,
21081        &abs_path,
21082        vec![
21083            (0, Breakpoint::new_standard()),
21084            (3, Breakpoint::new_log("hello world")),
21085        ],
21086    );
21087
21088    editor.update_in(cx, |editor, window, cx| {
21089        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21090    });
21091
21092    let breakpoints = editor.update(cx, |editor, cx| {
21093        editor
21094            .breakpoint_store()
21095            .as_ref()
21096            .unwrap()
21097            .read(cx)
21098            .all_source_breakpoints(cx)
21099            .clone()
21100    });
21101
21102    assert_breakpoint(
21103        &breakpoints,
21104        &abs_path,
21105        vec![
21106            (0, Breakpoint::new_standard()),
21107            (3, Breakpoint::new_log("hello Earth!!")),
21108        ],
21109    );
21110}
21111
21112/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21113/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21114/// or when breakpoints were placed out of order. This tests for a regression too
21115#[gpui::test]
21116async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21117    init_test(cx, |_| {});
21118
21119    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21120    let fs = FakeFs::new(cx.executor());
21121    fs.insert_tree(
21122        path!("/a"),
21123        json!({
21124            "main.rs": sample_text,
21125        }),
21126    )
21127    .await;
21128    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21129    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21130    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21131
21132    let fs = FakeFs::new(cx.executor());
21133    fs.insert_tree(
21134        path!("/a"),
21135        json!({
21136            "main.rs": sample_text,
21137        }),
21138    )
21139    .await;
21140    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21141    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21142    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21143    let worktree_id = workspace
21144        .update(cx, |workspace, _window, cx| {
21145            workspace.project().update(cx, |project, cx| {
21146                project.worktrees(cx).next().unwrap().read(cx).id()
21147            })
21148        })
21149        .unwrap();
21150
21151    let buffer = project
21152        .update(cx, |project, cx| {
21153            project.open_buffer((worktree_id, "main.rs"), cx)
21154        })
21155        .await
21156        .unwrap();
21157
21158    let (editor, cx) = cx.add_window_view(|window, cx| {
21159        Editor::new(
21160            EditorMode::full(),
21161            MultiBuffer::build_from_buffer(buffer, cx),
21162            Some(project.clone()),
21163            window,
21164            cx,
21165        )
21166    });
21167
21168    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21169    let abs_path = project.read_with(cx, |project, cx| {
21170        project
21171            .absolute_path(&project_path, cx)
21172            .map(|path_buf| Arc::from(path_buf.to_owned()))
21173            .unwrap()
21174    });
21175
21176    // assert we can add breakpoint on the first line
21177    editor.update_in(cx, |editor, window, cx| {
21178        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21179        editor.move_to_end(&MoveToEnd, window, cx);
21180        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21181        editor.move_up(&MoveUp, window, cx);
21182        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21183    });
21184
21185    let breakpoints = editor.update(cx, |editor, cx| {
21186        editor
21187            .breakpoint_store()
21188            .as_ref()
21189            .unwrap()
21190            .read(cx)
21191            .all_source_breakpoints(cx)
21192            .clone()
21193    });
21194
21195    assert_eq!(1, breakpoints.len());
21196    assert_breakpoint(
21197        &breakpoints,
21198        &abs_path,
21199        vec![
21200            (0, Breakpoint::new_standard()),
21201            (2, Breakpoint::new_standard()),
21202            (3, Breakpoint::new_standard()),
21203        ],
21204    );
21205
21206    editor.update_in(cx, |editor, window, cx| {
21207        editor.move_to_beginning(&MoveToBeginning, window, cx);
21208        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21209        editor.move_to_end(&MoveToEnd, window, cx);
21210        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21211        // Disabling a breakpoint that doesn't exist should do nothing
21212        editor.move_up(&MoveUp, window, cx);
21213        editor.move_up(&MoveUp, window, cx);
21214        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21215    });
21216
21217    let breakpoints = editor.update(cx, |editor, cx| {
21218        editor
21219            .breakpoint_store()
21220            .as_ref()
21221            .unwrap()
21222            .read(cx)
21223            .all_source_breakpoints(cx)
21224            .clone()
21225    });
21226
21227    let disable_breakpoint = {
21228        let mut bp = Breakpoint::new_standard();
21229        bp.state = BreakpointState::Disabled;
21230        bp
21231    };
21232
21233    assert_eq!(1, breakpoints.len());
21234    assert_breakpoint(
21235        &breakpoints,
21236        &abs_path,
21237        vec![
21238            (0, disable_breakpoint.clone()),
21239            (2, Breakpoint::new_standard()),
21240            (3, disable_breakpoint.clone()),
21241        ],
21242    );
21243
21244    editor.update_in(cx, |editor, window, cx| {
21245        editor.move_to_beginning(&MoveToBeginning, window, cx);
21246        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21247        editor.move_to_end(&MoveToEnd, window, cx);
21248        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21249        editor.move_up(&MoveUp, window, cx);
21250        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21251    });
21252
21253    let breakpoints = editor.update(cx, |editor, cx| {
21254        editor
21255            .breakpoint_store()
21256            .as_ref()
21257            .unwrap()
21258            .read(cx)
21259            .all_source_breakpoints(cx)
21260            .clone()
21261    });
21262
21263    assert_eq!(1, breakpoints.len());
21264    assert_breakpoint(
21265        &breakpoints,
21266        &abs_path,
21267        vec![
21268            (0, Breakpoint::new_standard()),
21269            (2, disable_breakpoint),
21270            (3, Breakpoint::new_standard()),
21271        ],
21272    );
21273}
21274
21275#[gpui::test]
21276async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21277    init_test(cx, |_| {});
21278    let capabilities = lsp::ServerCapabilities {
21279        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21280            prepare_provider: Some(true),
21281            work_done_progress_options: Default::default(),
21282        })),
21283        ..Default::default()
21284    };
21285    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21286
21287    cx.set_state(indoc! {"
21288        struct Fˇoo {}
21289    "});
21290
21291    cx.update_editor(|editor, _, cx| {
21292        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21293        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21294        editor.highlight_background::<DocumentHighlightRead>(
21295            &[highlight_range],
21296            |theme| theme.colors().editor_document_highlight_read_background,
21297            cx,
21298        );
21299    });
21300
21301    let mut prepare_rename_handler = cx
21302        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21303            move |_, _, _| async move {
21304                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21305                    start: lsp::Position {
21306                        line: 0,
21307                        character: 7,
21308                    },
21309                    end: lsp::Position {
21310                        line: 0,
21311                        character: 10,
21312                    },
21313                })))
21314            },
21315        );
21316    let prepare_rename_task = cx
21317        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21318        .expect("Prepare rename was not started");
21319    prepare_rename_handler.next().await.unwrap();
21320    prepare_rename_task.await.expect("Prepare rename failed");
21321
21322    let mut rename_handler =
21323        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21324            let edit = lsp::TextEdit {
21325                range: lsp::Range {
21326                    start: lsp::Position {
21327                        line: 0,
21328                        character: 7,
21329                    },
21330                    end: lsp::Position {
21331                        line: 0,
21332                        character: 10,
21333                    },
21334                },
21335                new_text: "FooRenamed".to_string(),
21336            };
21337            Ok(Some(lsp::WorkspaceEdit::new(
21338                // Specify the same edit twice
21339                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21340            )))
21341        });
21342    let rename_task = cx
21343        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21344        .expect("Confirm rename was not started");
21345    rename_handler.next().await.unwrap();
21346    rename_task.await.expect("Confirm rename failed");
21347    cx.run_until_parked();
21348
21349    // Despite two edits, only one is actually applied as those are identical
21350    cx.assert_editor_state(indoc! {"
21351        struct FooRenamedˇ {}
21352    "});
21353}
21354
21355#[gpui::test]
21356async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21357    init_test(cx, |_| {});
21358    // These capabilities indicate that the server does not support prepare rename.
21359    let capabilities = lsp::ServerCapabilities {
21360        rename_provider: Some(lsp::OneOf::Left(true)),
21361        ..Default::default()
21362    };
21363    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21364
21365    cx.set_state(indoc! {"
21366        struct Fˇoo {}
21367    "});
21368
21369    cx.update_editor(|editor, _window, cx| {
21370        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21371        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21372        editor.highlight_background::<DocumentHighlightRead>(
21373            &[highlight_range],
21374            |theme| theme.colors().editor_document_highlight_read_background,
21375            cx,
21376        );
21377    });
21378
21379    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21380        .expect("Prepare rename was not started")
21381        .await
21382        .expect("Prepare rename failed");
21383
21384    let mut rename_handler =
21385        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21386            let edit = lsp::TextEdit {
21387                range: lsp::Range {
21388                    start: lsp::Position {
21389                        line: 0,
21390                        character: 7,
21391                    },
21392                    end: lsp::Position {
21393                        line: 0,
21394                        character: 10,
21395                    },
21396                },
21397                new_text: "FooRenamed".to_string(),
21398            };
21399            Ok(Some(lsp::WorkspaceEdit::new(
21400                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21401            )))
21402        });
21403    let rename_task = cx
21404        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21405        .expect("Confirm rename was not started");
21406    rename_handler.next().await.unwrap();
21407    rename_task.await.expect("Confirm rename failed");
21408    cx.run_until_parked();
21409
21410    // Correct range is renamed, as `surrounding_word` is used to find it.
21411    cx.assert_editor_state(indoc! {"
21412        struct FooRenamedˇ {}
21413    "});
21414}
21415
21416#[gpui::test]
21417async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21418    init_test(cx, |_| {});
21419    let mut cx = EditorTestContext::new(cx).await;
21420
21421    let language = Arc::new(
21422        Language::new(
21423            LanguageConfig::default(),
21424            Some(tree_sitter_html::LANGUAGE.into()),
21425        )
21426        .with_brackets_query(
21427            r#"
21428            ("<" @open "/>" @close)
21429            ("</" @open ">" @close)
21430            ("<" @open ">" @close)
21431            ("\"" @open "\"" @close)
21432            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21433        "#,
21434        )
21435        .unwrap(),
21436    );
21437    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21438
21439    cx.set_state(indoc! {"
21440        <span>ˇ</span>
21441    "});
21442    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21443    cx.assert_editor_state(indoc! {"
21444        <span>
21445        ˇ
21446        </span>
21447    "});
21448
21449    cx.set_state(indoc! {"
21450        <span><span></span>ˇ</span>
21451    "});
21452    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21453    cx.assert_editor_state(indoc! {"
21454        <span><span></span>
21455        ˇ</span>
21456    "});
21457
21458    cx.set_state(indoc! {"
21459        <span>ˇ
21460        </span>
21461    "});
21462    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21463    cx.assert_editor_state(indoc! {"
21464        <span>
21465        ˇ
21466        </span>
21467    "});
21468}
21469
21470#[gpui::test(iterations = 10)]
21471async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21472    init_test(cx, |_| {});
21473
21474    let fs = FakeFs::new(cx.executor());
21475    fs.insert_tree(
21476        path!("/dir"),
21477        json!({
21478            "a.ts": "a",
21479        }),
21480    )
21481    .await;
21482
21483    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21484    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21485    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21486
21487    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21488    language_registry.add(Arc::new(Language::new(
21489        LanguageConfig {
21490            name: "TypeScript".into(),
21491            matcher: LanguageMatcher {
21492                path_suffixes: vec!["ts".to_string()],
21493                ..Default::default()
21494            },
21495            ..Default::default()
21496        },
21497        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21498    )));
21499    let mut fake_language_servers = language_registry.register_fake_lsp(
21500        "TypeScript",
21501        FakeLspAdapter {
21502            capabilities: lsp::ServerCapabilities {
21503                code_lens_provider: Some(lsp::CodeLensOptions {
21504                    resolve_provider: Some(true),
21505                }),
21506                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21507                    commands: vec!["_the/command".to_string()],
21508                    ..lsp::ExecuteCommandOptions::default()
21509                }),
21510                ..lsp::ServerCapabilities::default()
21511            },
21512            ..FakeLspAdapter::default()
21513        },
21514    );
21515
21516    let editor = workspace
21517        .update(cx, |workspace, window, cx| {
21518            workspace.open_abs_path(
21519                PathBuf::from(path!("/dir/a.ts")),
21520                OpenOptions::default(),
21521                window,
21522                cx,
21523            )
21524        })
21525        .unwrap()
21526        .await
21527        .unwrap()
21528        .downcast::<Editor>()
21529        .unwrap();
21530    cx.executor().run_until_parked();
21531
21532    let fake_server = fake_language_servers.next().await.unwrap();
21533
21534    let buffer = editor.update(cx, |editor, cx| {
21535        editor
21536            .buffer()
21537            .read(cx)
21538            .as_singleton()
21539            .expect("have opened a single file by path")
21540    });
21541
21542    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21543    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21544    drop(buffer_snapshot);
21545    let actions = cx
21546        .update_window(*workspace, |_, window, cx| {
21547            project.code_actions(&buffer, anchor..anchor, window, cx)
21548        })
21549        .unwrap();
21550
21551    fake_server
21552        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21553            Ok(Some(vec![
21554                lsp::CodeLens {
21555                    range: lsp::Range::default(),
21556                    command: Some(lsp::Command {
21557                        title: "Code lens command".to_owned(),
21558                        command: "_the/command".to_owned(),
21559                        arguments: None,
21560                    }),
21561                    data: None,
21562                },
21563                lsp::CodeLens {
21564                    range: lsp::Range::default(),
21565                    command: Some(lsp::Command {
21566                        title: "Command not in capabilities".to_owned(),
21567                        command: "not in capabilities".to_owned(),
21568                        arguments: None,
21569                    }),
21570                    data: None,
21571                },
21572                lsp::CodeLens {
21573                    range: lsp::Range {
21574                        start: lsp::Position {
21575                            line: 1,
21576                            character: 1,
21577                        },
21578                        end: lsp::Position {
21579                            line: 1,
21580                            character: 1,
21581                        },
21582                    },
21583                    command: Some(lsp::Command {
21584                        title: "Command not in range".to_owned(),
21585                        command: "_the/command".to_owned(),
21586                        arguments: None,
21587                    }),
21588                    data: None,
21589                },
21590            ]))
21591        })
21592        .next()
21593        .await;
21594
21595    let actions = actions.await.unwrap();
21596    assert_eq!(
21597        actions.len(),
21598        1,
21599        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21600    );
21601    let action = actions[0].clone();
21602    let apply = project.update(cx, |project, cx| {
21603        project.apply_code_action(buffer.clone(), action, true, cx)
21604    });
21605
21606    // Resolving the code action does not populate its edits. In absence of
21607    // edits, we must execute the given command.
21608    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21609        |mut lens, _| async move {
21610            let lens_command = lens.command.as_mut().expect("should have a command");
21611            assert_eq!(lens_command.title, "Code lens command");
21612            lens_command.arguments = Some(vec![json!("the-argument")]);
21613            Ok(lens)
21614        },
21615    );
21616
21617    // While executing the command, the language server sends the editor
21618    // a `workspaceEdit` request.
21619    fake_server
21620        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21621            let fake = fake_server.clone();
21622            move |params, _| {
21623                assert_eq!(params.command, "_the/command");
21624                let fake = fake.clone();
21625                async move {
21626                    fake.server
21627                        .request::<lsp::request::ApplyWorkspaceEdit>(
21628                            lsp::ApplyWorkspaceEditParams {
21629                                label: None,
21630                                edit: lsp::WorkspaceEdit {
21631                                    changes: Some(
21632                                        [(
21633                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21634                                            vec![lsp::TextEdit {
21635                                                range: lsp::Range::new(
21636                                                    lsp::Position::new(0, 0),
21637                                                    lsp::Position::new(0, 0),
21638                                                ),
21639                                                new_text: "X".into(),
21640                                            }],
21641                                        )]
21642                                        .into_iter()
21643                                        .collect(),
21644                                    ),
21645                                    ..lsp::WorkspaceEdit::default()
21646                                },
21647                            },
21648                        )
21649                        .await
21650                        .into_response()
21651                        .unwrap();
21652                    Ok(Some(json!(null)))
21653                }
21654            }
21655        })
21656        .next()
21657        .await;
21658
21659    // Applying the code lens command returns a project transaction containing the edits
21660    // sent by the language server in its `workspaceEdit` request.
21661    let transaction = apply.await.unwrap();
21662    assert!(transaction.0.contains_key(&buffer));
21663    buffer.update(cx, |buffer, cx| {
21664        assert_eq!(buffer.text(), "Xa");
21665        buffer.undo(cx);
21666        assert_eq!(buffer.text(), "a");
21667    });
21668
21669    let actions_after_edits = cx
21670        .update_window(*workspace, |_, window, cx| {
21671            project.code_actions(&buffer, anchor..anchor, window, cx)
21672        })
21673        .unwrap()
21674        .await
21675        .unwrap();
21676    assert_eq!(
21677        actions, actions_after_edits,
21678        "For the same selection, same code lens actions should be returned"
21679    );
21680
21681    let _responses =
21682        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21683            panic!("No more code lens requests are expected");
21684        });
21685    editor.update_in(cx, |editor, window, cx| {
21686        editor.select_all(&SelectAll, window, cx);
21687    });
21688    cx.executor().run_until_parked();
21689    let new_actions = cx
21690        .update_window(*workspace, |_, window, cx| {
21691            project.code_actions(&buffer, anchor..anchor, window, cx)
21692        })
21693        .unwrap()
21694        .await
21695        .unwrap();
21696    assert_eq!(
21697        actions, new_actions,
21698        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21699    );
21700}
21701
21702#[gpui::test]
21703async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21704    init_test(cx, |_| {});
21705
21706    let fs = FakeFs::new(cx.executor());
21707    let main_text = r#"fn main() {
21708println!("1");
21709println!("2");
21710println!("3");
21711println!("4");
21712println!("5");
21713}"#;
21714    let lib_text = "mod foo {}";
21715    fs.insert_tree(
21716        path!("/a"),
21717        json!({
21718            "lib.rs": lib_text,
21719            "main.rs": main_text,
21720        }),
21721    )
21722    .await;
21723
21724    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21725    let (workspace, cx) =
21726        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21727    let worktree_id = workspace.update(cx, |workspace, cx| {
21728        workspace.project().update(cx, |project, cx| {
21729            project.worktrees(cx).next().unwrap().read(cx).id()
21730        })
21731    });
21732
21733    let expected_ranges = vec![
21734        Point::new(0, 0)..Point::new(0, 0),
21735        Point::new(1, 0)..Point::new(1, 1),
21736        Point::new(2, 0)..Point::new(2, 2),
21737        Point::new(3, 0)..Point::new(3, 3),
21738    ];
21739
21740    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21741    let editor_1 = workspace
21742        .update_in(cx, |workspace, window, cx| {
21743            workspace.open_path(
21744                (worktree_id, "main.rs"),
21745                Some(pane_1.downgrade()),
21746                true,
21747                window,
21748                cx,
21749            )
21750        })
21751        .unwrap()
21752        .await
21753        .downcast::<Editor>()
21754        .unwrap();
21755    pane_1.update(cx, |pane, cx| {
21756        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21757        open_editor.update(cx, |editor, cx| {
21758            assert_eq!(
21759                editor.display_text(cx),
21760                main_text,
21761                "Original main.rs text on initial open",
21762            );
21763            assert_eq!(
21764                editor
21765                    .selections
21766                    .all::<Point>(cx)
21767                    .into_iter()
21768                    .map(|s| s.range())
21769                    .collect::<Vec<_>>(),
21770                vec![Point::zero()..Point::zero()],
21771                "Default selections on initial open",
21772            );
21773        })
21774    });
21775    editor_1.update_in(cx, |editor, window, cx| {
21776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21777            s.select_ranges(expected_ranges.clone());
21778        });
21779    });
21780
21781    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21782        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21783    });
21784    let editor_2 = workspace
21785        .update_in(cx, |workspace, window, cx| {
21786            workspace.open_path(
21787                (worktree_id, "main.rs"),
21788                Some(pane_2.downgrade()),
21789                true,
21790                window,
21791                cx,
21792            )
21793        })
21794        .unwrap()
21795        .await
21796        .downcast::<Editor>()
21797        .unwrap();
21798    pane_2.update(cx, |pane, cx| {
21799        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21800        open_editor.update(cx, |editor, cx| {
21801            assert_eq!(
21802                editor.display_text(cx),
21803                main_text,
21804                "Original main.rs text on initial open in another panel",
21805            );
21806            assert_eq!(
21807                editor
21808                    .selections
21809                    .all::<Point>(cx)
21810                    .into_iter()
21811                    .map(|s| s.range())
21812                    .collect::<Vec<_>>(),
21813                vec![Point::zero()..Point::zero()],
21814                "Default selections on initial open in another panel",
21815            );
21816        })
21817    });
21818
21819    editor_2.update_in(cx, |editor, window, cx| {
21820        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21821    });
21822
21823    let _other_editor_1 = workspace
21824        .update_in(cx, |workspace, window, cx| {
21825            workspace.open_path(
21826                (worktree_id, "lib.rs"),
21827                Some(pane_1.downgrade()),
21828                true,
21829                window,
21830                cx,
21831            )
21832        })
21833        .unwrap()
21834        .await
21835        .downcast::<Editor>()
21836        .unwrap();
21837    pane_1
21838        .update_in(cx, |pane, window, cx| {
21839            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21840        })
21841        .await
21842        .unwrap();
21843    drop(editor_1);
21844    pane_1.update(cx, |pane, cx| {
21845        pane.active_item()
21846            .unwrap()
21847            .downcast::<Editor>()
21848            .unwrap()
21849            .update(cx, |editor, cx| {
21850                assert_eq!(
21851                    editor.display_text(cx),
21852                    lib_text,
21853                    "Other file should be open and active",
21854                );
21855            });
21856        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21857    });
21858
21859    let _other_editor_2 = workspace
21860        .update_in(cx, |workspace, window, cx| {
21861            workspace.open_path(
21862                (worktree_id, "lib.rs"),
21863                Some(pane_2.downgrade()),
21864                true,
21865                window,
21866                cx,
21867            )
21868        })
21869        .unwrap()
21870        .await
21871        .downcast::<Editor>()
21872        .unwrap();
21873    pane_2
21874        .update_in(cx, |pane, window, cx| {
21875            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21876        })
21877        .await
21878        .unwrap();
21879    drop(editor_2);
21880    pane_2.update(cx, |pane, cx| {
21881        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21882        open_editor.update(cx, |editor, cx| {
21883            assert_eq!(
21884                editor.display_text(cx),
21885                lib_text,
21886                "Other file should be open and active in another panel too",
21887            );
21888        });
21889        assert_eq!(
21890            pane.items().count(),
21891            1,
21892            "No other editors should be open in another pane",
21893        );
21894    });
21895
21896    let _editor_1_reopened = workspace
21897        .update_in(cx, |workspace, window, cx| {
21898            workspace.open_path(
21899                (worktree_id, "main.rs"),
21900                Some(pane_1.downgrade()),
21901                true,
21902                window,
21903                cx,
21904            )
21905        })
21906        .unwrap()
21907        .await
21908        .downcast::<Editor>()
21909        .unwrap();
21910    let _editor_2_reopened = workspace
21911        .update_in(cx, |workspace, window, cx| {
21912            workspace.open_path(
21913                (worktree_id, "main.rs"),
21914                Some(pane_2.downgrade()),
21915                true,
21916                window,
21917                cx,
21918            )
21919        })
21920        .unwrap()
21921        .await
21922        .downcast::<Editor>()
21923        .unwrap();
21924    pane_1.update(cx, |pane, cx| {
21925        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21926        open_editor.update(cx, |editor, cx| {
21927            assert_eq!(
21928                editor.display_text(cx),
21929                main_text,
21930                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21931            );
21932            assert_eq!(
21933                editor
21934                    .selections
21935                    .all::<Point>(cx)
21936                    .into_iter()
21937                    .map(|s| s.range())
21938                    .collect::<Vec<_>>(),
21939                expected_ranges,
21940                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21941            );
21942        })
21943    });
21944    pane_2.update(cx, |pane, cx| {
21945        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21946        open_editor.update(cx, |editor, cx| {
21947            assert_eq!(
21948                editor.display_text(cx),
21949                r#"fn main() {
21950⋯rintln!("1");
21951⋯intln!("2");
21952⋯ntln!("3");
21953println!("4");
21954println!("5");
21955}"#,
21956                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21957            );
21958            assert_eq!(
21959                editor
21960                    .selections
21961                    .all::<Point>(cx)
21962                    .into_iter()
21963                    .map(|s| s.range())
21964                    .collect::<Vec<_>>(),
21965                vec![Point::zero()..Point::zero()],
21966                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21967            );
21968        })
21969    });
21970}
21971
21972#[gpui::test]
21973async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21974    init_test(cx, |_| {});
21975
21976    let fs = FakeFs::new(cx.executor());
21977    let main_text = r#"fn main() {
21978println!("1");
21979println!("2");
21980println!("3");
21981println!("4");
21982println!("5");
21983}"#;
21984    let lib_text = "mod foo {}";
21985    fs.insert_tree(
21986        path!("/a"),
21987        json!({
21988            "lib.rs": lib_text,
21989            "main.rs": main_text,
21990        }),
21991    )
21992    .await;
21993
21994    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21995    let (workspace, cx) =
21996        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21997    let worktree_id = workspace.update(cx, |workspace, cx| {
21998        workspace.project().update(cx, |project, cx| {
21999            project.worktrees(cx).next().unwrap().read(cx).id()
22000        })
22001    });
22002
22003    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22004    let editor = workspace
22005        .update_in(cx, |workspace, window, cx| {
22006            workspace.open_path(
22007                (worktree_id, "main.rs"),
22008                Some(pane.downgrade()),
22009                true,
22010                window,
22011                cx,
22012            )
22013        })
22014        .unwrap()
22015        .await
22016        .downcast::<Editor>()
22017        .unwrap();
22018    pane.update(cx, |pane, cx| {
22019        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22020        open_editor.update(cx, |editor, cx| {
22021            assert_eq!(
22022                editor.display_text(cx),
22023                main_text,
22024                "Original main.rs text on initial open",
22025            );
22026        })
22027    });
22028    editor.update_in(cx, |editor, window, cx| {
22029        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22030    });
22031
22032    cx.update_global(|store: &mut SettingsStore, cx| {
22033        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22034            s.restore_on_file_reopen = Some(false);
22035        });
22036    });
22037    editor.update_in(cx, |editor, window, cx| {
22038        editor.fold_ranges(
22039            vec![
22040                Point::new(1, 0)..Point::new(1, 1),
22041                Point::new(2, 0)..Point::new(2, 2),
22042                Point::new(3, 0)..Point::new(3, 3),
22043            ],
22044            false,
22045            window,
22046            cx,
22047        );
22048    });
22049    pane.update_in(cx, |pane, window, cx| {
22050        pane.close_all_items(&CloseAllItems::default(), window, cx)
22051    })
22052    .await
22053    .unwrap();
22054    pane.update(cx, |pane, _| {
22055        assert!(pane.active_item().is_none());
22056    });
22057    cx.update_global(|store: &mut SettingsStore, cx| {
22058        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22059            s.restore_on_file_reopen = Some(true);
22060        });
22061    });
22062
22063    let _editor_reopened = workspace
22064        .update_in(cx, |workspace, window, cx| {
22065            workspace.open_path(
22066                (worktree_id, "main.rs"),
22067                Some(pane.downgrade()),
22068                true,
22069                window,
22070                cx,
22071            )
22072        })
22073        .unwrap()
22074        .await
22075        .downcast::<Editor>()
22076        .unwrap();
22077    pane.update(cx, |pane, cx| {
22078        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22079        open_editor.update(cx, |editor, cx| {
22080            assert_eq!(
22081                editor.display_text(cx),
22082                main_text,
22083                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22084            );
22085        })
22086    });
22087}
22088
22089#[gpui::test]
22090async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22091    struct EmptyModalView {
22092        focus_handle: gpui::FocusHandle,
22093    }
22094    impl EventEmitter<DismissEvent> for EmptyModalView {}
22095    impl Render for EmptyModalView {
22096        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22097            div()
22098        }
22099    }
22100    impl Focusable for EmptyModalView {
22101        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22102            self.focus_handle.clone()
22103        }
22104    }
22105    impl workspace::ModalView for EmptyModalView {}
22106    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22107        EmptyModalView {
22108            focus_handle: cx.focus_handle(),
22109        }
22110    }
22111
22112    init_test(cx, |_| {});
22113
22114    let fs = FakeFs::new(cx.executor());
22115    let project = Project::test(fs, [], cx).await;
22116    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22117    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22118    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22119    let editor = cx.new_window_entity(|window, cx| {
22120        Editor::new(
22121            EditorMode::full(),
22122            buffer,
22123            Some(project.clone()),
22124            window,
22125            cx,
22126        )
22127    });
22128    workspace
22129        .update(cx, |workspace, window, cx| {
22130            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22131        })
22132        .unwrap();
22133    editor.update_in(cx, |editor, window, cx| {
22134        editor.open_context_menu(&OpenContextMenu, window, cx);
22135        assert!(editor.mouse_context_menu.is_some());
22136    });
22137    workspace
22138        .update(cx, |workspace, window, cx| {
22139            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22140        })
22141        .unwrap();
22142    cx.read(|cx| {
22143        assert!(editor.read(cx).mouse_context_menu.is_none());
22144    });
22145}
22146
22147#[gpui::test]
22148async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22149    init_test(cx, |_| {});
22150
22151    let fs = FakeFs::new(cx.executor());
22152    fs.insert_file(path!("/file.html"), Default::default())
22153        .await;
22154
22155    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22156
22157    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22158    let html_language = Arc::new(Language::new(
22159        LanguageConfig {
22160            name: "HTML".into(),
22161            matcher: LanguageMatcher {
22162                path_suffixes: vec!["html".to_string()],
22163                ..LanguageMatcher::default()
22164            },
22165            brackets: BracketPairConfig {
22166                pairs: vec![BracketPair {
22167                    start: "<".into(),
22168                    end: ">".into(),
22169                    close: true,
22170                    ..Default::default()
22171                }],
22172                ..Default::default()
22173            },
22174            ..Default::default()
22175        },
22176        Some(tree_sitter_html::LANGUAGE.into()),
22177    ));
22178    language_registry.add(html_language);
22179    let mut fake_servers = language_registry.register_fake_lsp(
22180        "HTML",
22181        FakeLspAdapter {
22182            capabilities: lsp::ServerCapabilities {
22183                completion_provider: Some(lsp::CompletionOptions {
22184                    resolve_provider: Some(true),
22185                    ..Default::default()
22186                }),
22187                ..Default::default()
22188            },
22189            ..Default::default()
22190        },
22191    );
22192
22193    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22194    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22195
22196    let worktree_id = workspace
22197        .update(cx, |workspace, _window, cx| {
22198            workspace.project().update(cx, |project, cx| {
22199                project.worktrees(cx).next().unwrap().read(cx).id()
22200            })
22201        })
22202        .unwrap();
22203    project
22204        .update(cx, |project, cx| {
22205            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22206        })
22207        .await
22208        .unwrap();
22209    let editor = workspace
22210        .update(cx, |workspace, window, cx| {
22211            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22212        })
22213        .unwrap()
22214        .await
22215        .unwrap()
22216        .downcast::<Editor>()
22217        .unwrap();
22218
22219    let fake_server = fake_servers.next().await.unwrap();
22220    editor.update_in(cx, |editor, window, cx| {
22221        editor.set_text("<ad></ad>", window, cx);
22222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22223            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22224        });
22225        let Some((buffer, _)) = editor
22226            .buffer
22227            .read(cx)
22228            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22229        else {
22230            panic!("Failed to get buffer for selection position");
22231        };
22232        let buffer = buffer.read(cx);
22233        let buffer_id = buffer.remote_id();
22234        let opening_range =
22235            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22236        let closing_range =
22237            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22238        let mut linked_ranges = HashMap::default();
22239        linked_ranges.insert(
22240            buffer_id,
22241            vec![(opening_range.clone(), vec![closing_range.clone()])],
22242        );
22243        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22244    });
22245    let mut completion_handle =
22246        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22247            Ok(Some(lsp::CompletionResponse::Array(vec![
22248                lsp::CompletionItem {
22249                    label: "head".to_string(),
22250                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22251                        lsp::InsertReplaceEdit {
22252                            new_text: "head".to_string(),
22253                            insert: lsp::Range::new(
22254                                lsp::Position::new(0, 1),
22255                                lsp::Position::new(0, 3),
22256                            ),
22257                            replace: lsp::Range::new(
22258                                lsp::Position::new(0, 1),
22259                                lsp::Position::new(0, 3),
22260                            ),
22261                        },
22262                    )),
22263                    ..Default::default()
22264                },
22265            ])))
22266        });
22267    editor.update_in(cx, |editor, window, cx| {
22268        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22269    });
22270    cx.run_until_parked();
22271    completion_handle.next().await.unwrap();
22272    editor.update(cx, |editor, _| {
22273        assert!(
22274            editor.context_menu_visible(),
22275            "Completion menu should be visible"
22276        );
22277    });
22278    editor.update_in(cx, |editor, window, cx| {
22279        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22280    });
22281    cx.executor().run_until_parked();
22282    editor.update(cx, |editor, cx| {
22283        assert_eq!(editor.text(cx), "<head></head>");
22284    });
22285}
22286
22287#[gpui::test]
22288async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22289    init_test(cx, |_| {});
22290
22291    let fs = FakeFs::new(cx.executor());
22292    fs.insert_tree(
22293        path!("/root"),
22294        json!({
22295            "a": {
22296                "main.rs": "fn main() {}",
22297            },
22298            "foo": {
22299                "bar": {
22300                    "external_file.rs": "pub mod external {}",
22301                }
22302            }
22303        }),
22304    )
22305    .await;
22306
22307    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22308    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22309    language_registry.add(rust_lang());
22310    let _fake_servers = language_registry.register_fake_lsp(
22311        "Rust",
22312        FakeLspAdapter {
22313            ..FakeLspAdapter::default()
22314        },
22315    );
22316    let (workspace, cx) =
22317        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22318    let worktree_id = workspace.update(cx, |workspace, cx| {
22319        workspace.project().update(cx, |project, cx| {
22320            project.worktrees(cx).next().unwrap().read(cx).id()
22321        })
22322    });
22323
22324    let assert_language_servers_count =
22325        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22326            project.update(cx, |project, cx| {
22327                let current = project
22328                    .lsp_store()
22329                    .read(cx)
22330                    .as_local()
22331                    .unwrap()
22332                    .language_servers
22333                    .len();
22334                assert_eq!(expected, current, "{context}");
22335            });
22336        };
22337
22338    assert_language_servers_count(
22339        0,
22340        "No servers should be running before any file is open",
22341        cx,
22342    );
22343    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22344    let main_editor = workspace
22345        .update_in(cx, |workspace, window, cx| {
22346            workspace.open_path(
22347                (worktree_id, "main.rs"),
22348                Some(pane.downgrade()),
22349                true,
22350                window,
22351                cx,
22352            )
22353        })
22354        .unwrap()
22355        .await
22356        .downcast::<Editor>()
22357        .unwrap();
22358    pane.update(cx, |pane, cx| {
22359        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22360        open_editor.update(cx, |editor, cx| {
22361            assert_eq!(
22362                editor.display_text(cx),
22363                "fn main() {}",
22364                "Original main.rs text on initial open",
22365            );
22366        });
22367        assert_eq!(open_editor, main_editor);
22368    });
22369    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22370
22371    let external_editor = workspace
22372        .update_in(cx, |workspace, window, cx| {
22373            workspace.open_abs_path(
22374                PathBuf::from("/root/foo/bar/external_file.rs"),
22375                OpenOptions::default(),
22376                window,
22377                cx,
22378            )
22379        })
22380        .await
22381        .expect("opening external file")
22382        .downcast::<Editor>()
22383        .expect("downcasted external file's open element to editor");
22384    pane.update(cx, |pane, cx| {
22385        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22386        open_editor.update(cx, |editor, cx| {
22387            assert_eq!(
22388                editor.display_text(cx),
22389                "pub mod external {}",
22390                "External file is open now",
22391            );
22392        });
22393        assert_eq!(open_editor, external_editor);
22394    });
22395    assert_language_servers_count(
22396        1,
22397        "Second, external, *.rs file should join the existing server",
22398        cx,
22399    );
22400
22401    pane.update_in(cx, |pane, window, cx| {
22402        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22403    })
22404    .await
22405    .unwrap();
22406    pane.update_in(cx, |pane, window, cx| {
22407        pane.navigate_backward(window, cx);
22408    });
22409    cx.run_until_parked();
22410    pane.update(cx, |pane, cx| {
22411        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22412        open_editor.update(cx, |editor, cx| {
22413            assert_eq!(
22414                editor.display_text(cx),
22415                "pub mod external {}",
22416                "External file is open now",
22417            );
22418        });
22419    });
22420    assert_language_servers_count(
22421        1,
22422        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22423        cx,
22424    );
22425
22426    cx.update(|_, cx| {
22427        workspace::reload(&workspace::Reload::default(), cx);
22428    });
22429    assert_language_servers_count(
22430        1,
22431        "After reloading the worktree with local and external files opened, only one project should be started",
22432        cx,
22433    );
22434}
22435
22436#[gpui::test]
22437async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22438    init_test(cx, |_| {});
22439
22440    let mut cx = EditorTestContext::new(cx).await;
22441    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22442    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22443
22444    // test cursor move to start of each line on tab
22445    // for `if`, `elif`, `else`, `while`, `with` and `for`
22446    cx.set_state(indoc! {"
22447        def main():
22448        ˇ    for item in items:
22449        ˇ        while item.active:
22450        ˇ            if item.value > 10:
22451        ˇ                continue
22452        ˇ            elif item.value < 0:
22453        ˇ                break
22454        ˇ            else:
22455        ˇ                with item.context() as ctx:
22456        ˇ                    yield count
22457        ˇ        else:
22458        ˇ            log('while else')
22459        ˇ    else:
22460        ˇ        log('for else')
22461    "});
22462    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22463    cx.assert_editor_state(indoc! {"
22464        def main():
22465            ˇfor item in items:
22466                ˇwhile item.active:
22467                    ˇif item.value > 10:
22468                        ˇcontinue
22469                    ˇelif item.value < 0:
22470                        ˇbreak
22471                    ˇelse:
22472                        ˇwith item.context() as ctx:
22473                            ˇyield count
22474                ˇelse:
22475                    ˇlog('while else')
22476            ˇelse:
22477                ˇlog('for else')
22478    "});
22479    // test relative indent is preserved when tab
22480    // for `if`, `elif`, `else`, `while`, `with` and `for`
22481    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22482    cx.assert_editor_state(indoc! {"
22483        def main():
22484                ˇfor item in items:
22485                    ˇwhile item.active:
22486                        ˇif item.value > 10:
22487                            ˇcontinue
22488                        ˇelif item.value < 0:
22489                            ˇbreak
22490                        ˇelse:
22491                            ˇwith item.context() as ctx:
22492                                ˇyield count
22493                    ˇelse:
22494                        ˇlog('while else')
22495                ˇelse:
22496                    ˇlog('for else')
22497    "});
22498
22499    // test cursor move to start of each line on tab
22500    // for `try`, `except`, `else`, `finally`, `match` and `def`
22501    cx.set_state(indoc! {"
22502        def main():
22503        ˇ    try:
22504        ˇ        fetch()
22505        ˇ    except ValueError:
22506        ˇ        handle_error()
22507        ˇ    else:
22508        ˇ        match value:
22509        ˇ            case _:
22510        ˇ    finally:
22511        ˇ        def status():
22512        ˇ            return 0
22513    "});
22514    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22515    cx.assert_editor_state(indoc! {"
22516        def main():
22517            ˇtry:
22518                ˇfetch()
22519            ˇexcept ValueError:
22520                ˇhandle_error()
22521            ˇelse:
22522                ˇmatch value:
22523                    ˇcase _:
22524            ˇfinally:
22525                ˇdef status():
22526                    ˇreturn 0
22527    "});
22528    // test relative indent is preserved when tab
22529    // for `try`, `except`, `else`, `finally`, `match` and `def`
22530    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22531    cx.assert_editor_state(indoc! {"
22532        def main():
22533                ˇtry:
22534                    ˇfetch()
22535                ˇexcept ValueError:
22536                    ˇhandle_error()
22537                ˇelse:
22538                    ˇmatch value:
22539                        ˇcase _:
22540                ˇfinally:
22541                    ˇdef status():
22542                        ˇreturn 0
22543    "});
22544}
22545
22546#[gpui::test]
22547async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22548    init_test(cx, |_| {});
22549
22550    let mut cx = EditorTestContext::new(cx).await;
22551    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22552    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22553
22554    // test `else` auto outdents when typed inside `if` block
22555    cx.set_state(indoc! {"
22556        def main():
22557            if i == 2:
22558                return
22559                ˇ
22560    "});
22561    cx.update_editor(|editor, window, cx| {
22562        editor.handle_input("else:", window, cx);
22563    });
22564    cx.assert_editor_state(indoc! {"
22565        def main():
22566            if i == 2:
22567                return
22568            else:ˇ
22569    "});
22570
22571    // test `except` auto outdents when typed inside `try` block
22572    cx.set_state(indoc! {"
22573        def main():
22574            try:
22575                i = 2
22576                ˇ
22577    "});
22578    cx.update_editor(|editor, window, cx| {
22579        editor.handle_input("except:", window, cx);
22580    });
22581    cx.assert_editor_state(indoc! {"
22582        def main():
22583            try:
22584                i = 2
22585            except:ˇ
22586    "});
22587
22588    // test `else` auto outdents when typed inside `except` block
22589    cx.set_state(indoc! {"
22590        def main():
22591            try:
22592                i = 2
22593            except:
22594                j = 2
22595                ˇ
22596    "});
22597    cx.update_editor(|editor, window, cx| {
22598        editor.handle_input("else:", window, cx);
22599    });
22600    cx.assert_editor_state(indoc! {"
22601        def main():
22602            try:
22603                i = 2
22604            except:
22605                j = 2
22606            else:ˇ
22607    "});
22608
22609    // test `finally` auto outdents when typed inside `else` block
22610    cx.set_state(indoc! {"
22611        def main():
22612            try:
22613                i = 2
22614            except:
22615                j = 2
22616            else:
22617                k = 2
22618                ˇ
22619    "});
22620    cx.update_editor(|editor, window, cx| {
22621        editor.handle_input("finally:", window, cx);
22622    });
22623    cx.assert_editor_state(indoc! {"
22624        def main():
22625            try:
22626                i = 2
22627            except:
22628                j = 2
22629            else:
22630                k = 2
22631            finally:ˇ
22632    "});
22633
22634    // test `else` does not outdents when typed inside `except` block right after for block
22635    cx.set_state(indoc! {"
22636        def main():
22637            try:
22638                i = 2
22639            except:
22640                for i in range(n):
22641                    pass
22642                ˇ
22643    "});
22644    cx.update_editor(|editor, window, cx| {
22645        editor.handle_input("else:", window, cx);
22646    });
22647    cx.assert_editor_state(indoc! {"
22648        def main():
22649            try:
22650                i = 2
22651            except:
22652                for i in range(n):
22653                    pass
22654                else:ˇ
22655    "});
22656
22657    // test `finally` auto outdents when typed inside `else` block right after for block
22658    cx.set_state(indoc! {"
22659        def main():
22660            try:
22661                i = 2
22662            except:
22663                j = 2
22664            else:
22665                for i in range(n):
22666                    pass
22667                ˇ
22668    "});
22669    cx.update_editor(|editor, window, cx| {
22670        editor.handle_input("finally:", window, cx);
22671    });
22672    cx.assert_editor_state(indoc! {"
22673        def main():
22674            try:
22675                i = 2
22676            except:
22677                j = 2
22678            else:
22679                for i in range(n):
22680                    pass
22681            finally:ˇ
22682    "});
22683
22684    // test `except` outdents to inner "try" block
22685    cx.set_state(indoc! {"
22686        def main():
22687            try:
22688                i = 2
22689                if i == 2:
22690                    try:
22691                        i = 3
22692                        ˇ
22693    "});
22694    cx.update_editor(|editor, window, cx| {
22695        editor.handle_input("except:", window, cx);
22696    });
22697    cx.assert_editor_state(indoc! {"
22698        def main():
22699            try:
22700                i = 2
22701                if i == 2:
22702                    try:
22703                        i = 3
22704                    except:ˇ
22705    "});
22706
22707    // test `except` outdents to outer "try" block
22708    cx.set_state(indoc! {"
22709        def main():
22710            try:
22711                i = 2
22712                if i == 2:
22713                    try:
22714                        i = 3
22715                ˇ
22716    "});
22717    cx.update_editor(|editor, window, cx| {
22718        editor.handle_input("except:", window, cx);
22719    });
22720    cx.assert_editor_state(indoc! {"
22721        def main():
22722            try:
22723                i = 2
22724                if i == 2:
22725                    try:
22726                        i = 3
22727            except:ˇ
22728    "});
22729
22730    // test `else` stays at correct indent when typed after `for` block
22731    cx.set_state(indoc! {"
22732        def main():
22733            for i in range(10):
22734                if i == 3:
22735                    break
22736            ˇ
22737    "});
22738    cx.update_editor(|editor, window, cx| {
22739        editor.handle_input("else:", window, cx);
22740    });
22741    cx.assert_editor_state(indoc! {"
22742        def main():
22743            for i in range(10):
22744                if i == 3:
22745                    break
22746            else:ˇ
22747    "});
22748
22749    // test does not outdent on typing after line with square brackets
22750    cx.set_state(indoc! {"
22751        def f() -> list[str]:
22752            ˇ
22753    "});
22754    cx.update_editor(|editor, window, cx| {
22755        editor.handle_input("a", window, cx);
22756    });
22757    cx.assert_editor_state(indoc! {"
22758        def f() -> list[str]:
2275922760    "});
22761
22762    // test does not outdent on typing : after case keyword
22763    cx.set_state(indoc! {"
22764        match 1:
22765            caseˇ
22766    "});
22767    cx.update_editor(|editor, window, cx| {
22768        editor.handle_input(":", window, cx);
22769    });
22770    cx.assert_editor_state(indoc! {"
22771        match 1:
22772            case:ˇ
22773    "});
22774}
22775
22776#[gpui::test]
22777async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22778    init_test(cx, |_| {});
22779    update_test_language_settings(cx, |settings| {
22780        settings.defaults.extend_comment_on_newline = Some(false);
22781    });
22782    let mut cx = EditorTestContext::new(cx).await;
22783    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22784    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22785
22786    // test correct indent after newline on comment
22787    cx.set_state(indoc! {"
22788        # COMMENT:ˇ
22789    "});
22790    cx.update_editor(|editor, window, cx| {
22791        editor.newline(&Newline, window, cx);
22792    });
22793    cx.assert_editor_state(indoc! {"
22794        # COMMENT:
22795        ˇ
22796    "});
22797
22798    // test correct indent after newline in brackets
22799    cx.set_state(indoc! {"
22800        {ˇ}
22801    "});
22802    cx.update_editor(|editor, window, cx| {
22803        editor.newline(&Newline, window, cx);
22804    });
22805    cx.run_until_parked();
22806    cx.assert_editor_state(indoc! {"
22807        {
22808            ˇ
22809        }
22810    "});
22811
22812    cx.set_state(indoc! {"
22813        (ˇ)
22814    "});
22815    cx.update_editor(|editor, window, cx| {
22816        editor.newline(&Newline, window, cx);
22817    });
22818    cx.run_until_parked();
22819    cx.assert_editor_state(indoc! {"
22820        (
22821            ˇ
22822        )
22823    "});
22824
22825    // do not indent after empty lists or dictionaries
22826    cx.set_state(indoc! {"
22827        a = []ˇ
22828    "});
22829    cx.update_editor(|editor, window, cx| {
22830        editor.newline(&Newline, window, cx);
22831    });
22832    cx.run_until_parked();
22833    cx.assert_editor_state(indoc! {"
22834        a = []
22835        ˇ
22836    "});
22837}
22838
22839#[gpui::test]
22840async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22841    init_test(cx, |_| {});
22842
22843    let mut cx = EditorTestContext::new(cx).await;
22844    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22845    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22846
22847    // test cursor move to start of each line on tab
22848    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22849    cx.set_state(indoc! {"
22850        function main() {
22851        ˇ    for item in $items; do
22852        ˇ        while [ -n \"$item\" ]; do
22853        ˇ            if [ \"$value\" -gt 10 ]; then
22854        ˇ                continue
22855        ˇ            elif [ \"$value\" -lt 0 ]; then
22856        ˇ                break
22857        ˇ            else
22858        ˇ                echo \"$item\"
22859        ˇ            fi
22860        ˇ        done
22861        ˇ    done
22862        ˇ}
22863    "});
22864    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22865    cx.assert_editor_state(indoc! {"
22866        function main() {
22867            ˇfor item in $items; do
22868                ˇwhile [ -n \"$item\" ]; do
22869                    ˇif [ \"$value\" -gt 10 ]; then
22870                        ˇcontinue
22871                    ˇelif [ \"$value\" -lt 0 ]; then
22872                        ˇbreak
22873                    ˇelse
22874                        ˇecho \"$item\"
22875                    ˇfi
22876                ˇdone
22877            ˇdone
22878        ˇ}
22879    "});
22880    // test relative indent is preserved when tab
22881    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22882    cx.assert_editor_state(indoc! {"
22883        function main() {
22884                ˇfor item in $items; do
22885                    ˇwhile [ -n \"$item\" ]; do
22886                        ˇif [ \"$value\" -gt 10 ]; then
22887                            ˇcontinue
22888                        ˇelif [ \"$value\" -lt 0 ]; then
22889                            ˇbreak
22890                        ˇelse
22891                            ˇecho \"$item\"
22892                        ˇfi
22893                    ˇdone
22894                ˇdone
22895            ˇ}
22896    "});
22897
22898    // test cursor move to start of each line on tab
22899    // for `case` statement with patterns
22900    cx.set_state(indoc! {"
22901        function handle() {
22902        ˇ    case \"$1\" in
22903        ˇ        start)
22904        ˇ            echo \"a\"
22905        ˇ            ;;
22906        ˇ        stop)
22907        ˇ            echo \"b\"
22908        ˇ            ;;
22909        ˇ        *)
22910        ˇ            echo \"c\"
22911        ˇ            ;;
22912        ˇ    esac
22913        ˇ}
22914    "});
22915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22916    cx.assert_editor_state(indoc! {"
22917        function handle() {
22918            ˇcase \"$1\" in
22919                ˇstart)
22920                    ˇecho \"a\"
22921                    ˇ;;
22922                ˇstop)
22923                    ˇecho \"b\"
22924                    ˇ;;
22925                ˇ*)
22926                    ˇecho \"c\"
22927                    ˇ;;
22928            ˇesac
22929        ˇ}
22930    "});
22931}
22932
22933#[gpui::test]
22934async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
22935    init_test(cx, |_| {});
22936
22937    let mut cx = EditorTestContext::new(cx).await;
22938    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22939    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22940
22941    // test indents on comment insert
22942    cx.set_state(indoc! {"
22943        function main() {
22944        ˇ    for item in $items; do
22945        ˇ        while [ -n \"$item\" ]; do
22946        ˇ            if [ \"$value\" -gt 10 ]; then
22947        ˇ                continue
22948        ˇ            elif [ \"$value\" -lt 0 ]; then
22949        ˇ                break
22950        ˇ            else
22951        ˇ                echo \"$item\"
22952        ˇ            fi
22953        ˇ        done
22954        ˇ    done
22955        ˇ}
22956    "});
22957    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
22958    cx.assert_editor_state(indoc! {"
22959        function main() {
22960        #ˇ    for item in $items; do
22961        #ˇ        while [ -n \"$item\" ]; do
22962        #ˇ            if [ \"$value\" -gt 10 ]; then
22963        #ˇ                continue
22964        #ˇ            elif [ \"$value\" -lt 0 ]; then
22965        #ˇ                break
22966        #ˇ            else
22967        #ˇ                echo \"$item\"
22968        #ˇ            fi
22969        #ˇ        done
22970        #ˇ    done
22971        #ˇ}
22972    "});
22973}
22974
22975#[gpui::test]
22976async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
22977    init_test(cx, |_| {});
22978
22979    let mut cx = EditorTestContext::new(cx).await;
22980    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22981    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22982
22983    // test `else` auto outdents when typed inside `if` block
22984    cx.set_state(indoc! {"
22985        if [ \"$1\" = \"test\" ]; then
22986            echo \"foo bar\"
22987            ˇ
22988    "});
22989    cx.update_editor(|editor, window, cx| {
22990        editor.handle_input("else", window, cx);
22991    });
22992    cx.assert_editor_state(indoc! {"
22993        if [ \"$1\" = \"test\" ]; then
22994            echo \"foo bar\"
22995        elseˇ
22996    "});
22997
22998    // test `elif` auto outdents when typed inside `if` block
22999    cx.set_state(indoc! {"
23000        if [ \"$1\" = \"test\" ]; then
23001            echo \"foo bar\"
23002            ˇ
23003    "});
23004    cx.update_editor(|editor, window, cx| {
23005        editor.handle_input("elif", window, cx);
23006    });
23007    cx.assert_editor_state(indoc! {"
23008        if [ \"$1\" = \"test\" ]; then
23009            echo \"foo bar\"
23010        elifˇ
23011    "});
23012
23013    // test `fi` auto outdents when typed inside `else` block
23014    cx.set_state(indoc! {"
23015        if [ \"$1\" = \"test\" ]; then
23016            echo \"foo bar\"
23017        else
23018            echo \"bar baz\"
23019            ˇ
23020    "});
23021    cx.update_editor(|editor, window, cx| {
23022        editor.handle_input("fi", window, cx);
23023    });
23024    cx.assert_editor_state(indoc! {"
23025        if [ \"$1\" = \"test\" ]; then
23026            echo \"foo bar\"
23027        else
23028            echo \"bar baz\"
23029        fiˇ
23030    "});
23031
23032    // test `done` auto outdents when typed inside `while` block
23033    cx.set_state(indoc! {"
23034        while read line; do
23035            echo \"$line\"
23036            ˇ
23037    "});
23038    cx.update_editor(|editor, window, cx| {
23039        editor.handle_input("done", window, cx);
23040    });
23041    cx.assert_editor_state(indoc! {"
23042        while read line; do
23043            echo \"$line\"
23044        doneˇ
23045    "});
23046
23047    // test `done` auto outdents when typed inside `for` block
23048    cx.set_state(indoc! {"
23049        for file in *.txt; do
23050            cat \"$file\"
23051            ˇ
23052    "});
23053    cx.update_editor(|editor, window, cx| {
23054        editor.handle_input("done", window, cx);
23055    });
23056    cx.assert_editor_state(indoc! {"
23057        for file in *.txt; do
23058            cat \"$file\"
23059        doneˇ
23060    "});
23061
23062    // test `esac` auto outdents when typed inside `case` block
23063    cx.set_state(indoc! {"
23064        case \"$1\" in
23065            start)
23066                echo \"foo bar\"
23067                ;;
23068            stop)
23069                echo \"bar baz\"
23070                ;;
23071            ˇ
23072    "});
23073    cx.update_editor(|editor, window, cx| {
23074        editor.handle_input("esac", window, cx);
23075    });
23076    cx.assert_editor_state(indoc! {"
23077        case \"$1\" in
23078            start)
23079                echo \"foo bar\"
23080                ;;
23081            stop)
23082                echo \"bar baz\"
23083                ;;
23084        esacˇ
23085    "});
23086
23087    // test `*)` auto outdents when typed inside `case` block
23088    cx.set_state(indoc! {"
23089        case \"$1\" in
23090            start)
23091                echo \"foo bar\"
23092                ;;
23093                ˇ
23094    "});
23095    cx.update_editor(|editor, window, cx| {
23096        editor.handle_input("*)", window, cx);
23097    });
23098    cx.assert_editor_state(indoc! {"
23099        case \"$1\" in
23100            start)
23101                echo \"foo bar\"
23102                ;;
23103            *)ˇ
23104    "});
23105
23106    // test `fi` outdents to correct level with nested if blocks
23107    cx.set_state(indoc! {"
23108        if [ \"$1\" = \"test\" ]; then
23109            echo \"outer if\"
23110            if [ \"$2\" = \"debug\" ]; then
23111                echo \"inner if\"
23112                ˇ
23113    "});
23114    cx.update_editor(|editor, window, cx| {
23115        editor.handle_input("fi", window, cx);
23116    });
23117    cx.assert_editor_state(indoc! {"
23118        if [ \"$1\" = \"test\" ]; then
23119            echo \"outer if\"
23120            if [ \"$2\" = \"debug\" ]; then
23121                echo \"inner if\"
23122            fiˇ
23123    "});
23124}
23125
23126#[gpui::test]
23127async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23128    init_test(cx, |_| {});
23129    update_test_language_settings(cx, |settings| {
23130        settings.defaults.extend_comment_on_newline = Some(false);
23131    });
23132    let mut cx = EditorTestContext::new(cx).await;
23133    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23134    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23135
23136    // test correct indent after newline on comment
23137    cx.set_state(indoc! {"
23138        # COMMENT:ˇ
23139    "});
23140    cx.update_editor(|editor, window, cx| {
23141        editor.newline(&Newline, window, cx);
23142    });
23143    cx.assert_editor_state(indoc! {"
23144        # COMMENT:
23145        ˇ
23146    "});
23147
23148    // test correct indent after newline after `then`
23149    cx.set_state(indoc! {"
23150
23151        if [ \"$1\" = \"test\" ]; thenˇ
23152    "});
23153    cx.update_editor(|editor, window, cx| {
23154        editor.newline(&Newline, window, cx);
23155    });
23156    cx.run_until_parked();
23157    cx.assert_editor_state(indoc! {"
23158
23159        if [ \"$1\" = \"test\" ]; then
23160            ˇ
23161    "});
23162
23163    // test correct indent after newline after `else`
23164    cx.set_state(indoc! {"
23165        if [ \"$1\" = \"test\" ]; then
23166        elseˇ
23167    "});
23168    cx.update_editor(|editor, window, cx| {
23169        editor.newline(&Newline, window, cx);
23170    });
23171    cx.run_until_parked();
23172    cx.assert_editor_state(indoc! {"
23173        if [ \"$1\" = \"test\" ]; then
23174        else
23175            ˇ
23176    "});
23177
23178    // test correct indent after newline after `elif`
23179    cx.set_state(indoc! {"
23180        if [ \"$1\" = \"test\" ]; then
23181        elifˇ
23182    "});
23183    cx.update_editor(|editor, window, cx| {
23184        editor.newline(&Newline, window, cx);
23185    });
23186    cx.run_until_parked();
23187    cx.assert_editor_state(indoc! {"
23188        if [ \"$1\" = \"test\" ]; then
23189        elif
23190            ˇ
23191    "});
23192
23193    // test correct indent after newline after `do`
23194    cx.set_state(indoc! {"
23195        for file in *.txt; doˇ
23196    "});
23197    cx.update_editor(|editor, window, cx| {
23198        editor.newline(&Newline, window, cx);
23199    });
23200    cx.run_until_parked();
23201    cx.assert_editor_state(indoc! {"
23202        for file in *.txt; do
23203            ˇ
23204    "});
23205
23206    // test correct indent after newline after case pattern
23207    cx.set_state(indoc! {"
23208        case \"$1\" in
23209            start)ˇ
23210    "});
23211    cx.update_editor(|editor, window, cx| {
23212        editor.newline(&Newline, window, cx);
23213    });
23214    cx.run_until_parked();
23215    cx.assert_editor_state(indoc! {"
23216        case \"$1\" in
23217            start)
23218                ˇ
23219    "});
23220
23221    // test correct indent after newline after case pattern
23222    cx.set_state(indoc! {"
23223        case \"$1\" in
23224            start)
23225                ;;
23226            *)ˇ
23227    "});
23228    cx.update_editor(|editor, window, cx| {
23229        editor.newline(&Newline, window, cx);
23230    });
23231    cx.run_until_parked();
23232    cx.assert_editor_state(indoc! {"
23233        case \"$1\" in
23234            start)
23235                ;;
23236            *)
23237                ˇ
23238    "});
23239
23240    // test correct indent after newline after function opening brace
23241    cx.set_state(indoc! {"
23242        function test() {ˇ}
23243    "});
23244    cx.update_editor(|editor, window, cx| {
23245        editor.newline(&Newline, window, cx);
23246    });
23247    cx.run_until_parked();
23248    cx.assert_editor_state(indoc! {"
23249        function test() {
23250            ˇ
23251        }
23252    "});
23253
23254    // test no extra indent after semicolon on same line
23255    cx.set_state(indoc! {"
23256        echo \"test\"23257    "});
23258    cx.update_editor(|editor, window, cx| {
23259        editor.newline(&Newline, window, cx);
23260    });
23261    cx.run_until_parked();
23262    cx.assert_editor_state(indoc! {"
23263        echo \"test\";
23264        ˇ
23265    "});
23266}
23267
23268fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23269    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23270    point..point
23271}
23272
23273#[track_caller]
23274fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23275    let (text, ranges) = marked_text_ranges(marked_text, true);
23276    assert_eq!(editor.text(cx), text);
23277    assert_eq!(
23278        editor.selections.ranges(cx),
23279        ranges,
23280        "Assert selections are {}",
23281        marked_text
23282    );
23283}
23284
23285pub fn handle_signature_help_request(
23286    cx: &mut EditorLspTestContext,
23287    mocked_response: lsp::SignatureHelp,
23288) -> impl Future<Output = ()> + use<> {
23289    let mut request =
23290        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23291            let mocked_response = mocked_response.clone();
23292            async move { Ok(Some(mocked_response)) }
23293        });
23294
23295    async move {
23296        request.next().await;
23297    }
23298}
23299
23300#[track_caller]
23301pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23302    cx.update_editor(|editor, _, _| {
23303        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23304            let entries = menu.entries.borrow();
23305            let entries = entries
23306                .iter()
23307                .map(|entry| entry.string.as_str())
23308                .collect::<Vec<_>>();
23309            assert_eq!(entries, expected);
23310        } else {
23311            panic!("Expected completions menu");
23312        }
23313    });
23314}
23315
23316/// Handle completion request passing a marked string specifying where the completion
23317/// should be triggered from using '|' character, what range should be replaced, and what completions
23318/// should be returned using '<' and '>' to delimit the range.
23319///
23320/// Also see `handle_completion_request_with_insert_and_replace`.
23321#[track_caller]
23322pub fn handle_completion_request(
23323    marked_string: &str,
23324    completions: Vec<&'static str>,
23325    is_incomplete: bool,
23326    counter: Arc<AtomicUsize>,
23327    cx: &mut EditorLspTestContext,
23328) -> impl Future<Output = ()> {
23329    let complete_from_marker: TextRangeMarker = '|'.into();
23330    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23331    let (_, mut marked_ranges) = marked_text_ranges_by(
23332        marked_string,
23333        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23334    );
23335
23336    let complete_from_position =
23337        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23338    let replace_range =
23339        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23340
23341    let mut request =
23342        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23343            let completions = completions.clone();
23344            counter.fetch_add(1, atomic::Ordering::Release);
23345            async move {
23346                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23347                assert_eq!(
23348                    params.text_document_position.position,
23349                    complete_from_position
23350                );
23351                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23352                    is_incomplete: is_incomplete,
23353                    item_defaults: None,
23354                    items: completions
23355                        .iter()
23356                        .map(|completion_text| lsp::CompletionItem {
23357                            label: completion_text.to_string(),
23358                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23359                                range: replace_range,
23360                                new_text: completion_text.to_string(),
23361                            })),
23362                            ..Default::default()
23363                        })
23364                        .collect(),
23365                })))
23366            }
23367        });
23368
23369    async move {
23370        request.next().await;
23371    }
23372}
23373
23374/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23375/// given instead, which also contains an `insert` range.
23376///
23377/// This function uses markers to define ranges:
23378/// - `|` marks the cursor position
23379/// - `<>` marks the replace range
23380/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23381pub fn handle_completion_request_with_insert_and_replace(
23382    cx: &mut EditorLspTestContext,
23383    marked_string: &str,
23384    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23385    counter: Arc<AtomicUsize>,
23386) -> impl Future<Output = ()> {
23387    let complete_from_marker: TextRangeMarker = '|'.into();
23388    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23389    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23390
23391    let (_, mut marked_ranges) = marked_text_ranges_by(
23392        marked_string,
23393        vec![
23394            complete_from_marker.clone(),
23395            replace_range_marker.clone(),
23396            insert_range_marker.clone(),
23397        ],
23398    );
23399
23400    let complete_from_position =
23401        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23402    let replace_range =
23403        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23404
23405    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23406        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23407        _ => lsp::Range {
23408            start: replace_range.start,
23409            end: complete_from_position,
23410        },
23411    };
23412
23413    let mut request =
23414        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23415            let completions = completions.clone();
23416            counter.fetch_add(1, atomic::Ordering::Release);
23417            async move {
23418                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23419                assert_eq!(
23420                    params.text_document_position.position, complete_from_position,
23421                    "marker `|` position doesn't match",
23422                );
23423                Ok(Some(lsp::CompletionResponse::Array(
23424                    completions
23425                        .iter()
23426                        .map(|(label, new_text)| lsp::CompletionItem {
23427                            label: label.to_string(),
23428                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23429                                lsp::InsertReplaceEdit {
23430                                    insert: insert_range,
23431                                    replace: replace_range,
23432                                    new_text: new_text.to_string(),
23433                                },
23434                            )),
23435                            ..Default::default()
23436                        })
23437                        .collect(),
23438                )))
23439            }
23440        });
23441
23442    async move {
23443        request.next().await;
23444    }
23445}
23446
23447fn handle_resolve_completion_request(
23448    cx: &mut EditorLspTestContext,
23449    edits: Option<Vec<(&'static str, &'static str)>>,
23450) -> impl Future<Output = ()> {
23451    let edits = edits.map(|edits| {
23452        edits
23453            .iter()
23454            .map(|(marked_string, new_text)| {
23455                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23456                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23457                lsp::TextEdit::new(replace_range, new_text.to_string())
23458            })
23459            .collect::<Vec<_>>()
23460    });
23461
23462    let mut request =
23463        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23464            let edits = edits.clone();
23465            async move {
23466                Ok(lsp::CompletionItem {
23467                    additional_text_edits: edits,
23468                    ..Default::default()
23469                })
23470            }
23471        });
23472
23473    async move {
23474        request.next().await;
23475    }
23476}
23477
23478pub(crate) fn update_test_language_settings(
23479    cx: &mut TestAppContext,
23480    f: impl Fn(&mut AllLanguageSettingsContent),
23481) {
23482    cx.update(|cx| {
23483        SettingsStore::update_global(cx, |store, cx| {
23484            store.update_user_settings::<AllLanguageSettings>(cx, f);
23485        });
23486    });
23487}
23488
23489pub(crate) fn update_test_project_settings(
23490    cx: &mut TestAppContext,
23491    f: impl Fn(&mut ProjectSettings),
23492) {
23493    cx.update(|cx| {
23494        SettingsStore::update_global(cx, |store, cx| {
23495            store.update_user_settings::<ProjectSettings>(cx, f);
23496        });
23497    });
23498}
23499
23500pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23501    cx.update(|cx| {
23502        assets::Assets.load_test_fonts(cx);
23503        let store = SettingsStore::test(cx);
23504        cx.set_global(store);
23505        theme::init(theme::LoadThemes::JustBase, cx);
23506        release_channel::init(SemanticVersion::default(), cx);
23507        client::init_settings(cx);
23508        language::init(cx);
23509        Project::init_settings(cx);
23510        workspace::init_settings(cx);
23511        crate::init(cx);
23512    });
23513    zlog::init_test();
23514    update_test_language_settings(cx, f);
23515}
23516
23517#[track_caller]
23518fn assert_hunk_revert(
23519    not_reverted_text_with_selections: &str,
23520    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23521    expected_reverted_text_with_selections: &str,
23522    base_text: &str,
23523    cx: &mut EditorLspTestContext,
23524) {
23525    cx.set_state(not_reverted_text_with_selections);
23526    cx.set_head_text(base_text);
23527    cx.executor().run_until_parked();
23528
23529    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23530        let snapshot = editor.snapshot(window, cx);
23531        let reverted_hunk_statuses = snapshot
23532            .buffer_snapshot
23533            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23534            .map(|hunk| hunk.status().kind)
23535            .collect::<Vec<_>>();
23536
23537        editor.git_restore(&Default::default(), window, cx);
23538        reverted_hunk_statuses
23539    });
23540    cx.executor().run_until_parked();
23541    cx.assert_editor_state(expected_reverted_text_with_selections);
23542    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23543}
23544
23545#[gpui::test(iterations = 10)]
23546async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23547    init_test(cx, |_| {});
23548
23549    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23550    let counter = diagnostic_requests.clone();
23551
23552    let fs = FakeFs::new(cx.executor());
23553    fs.insert_tree(
23554        path!("/a"),
23555        json!({
23556            "first.rs": "fn main() { let a = 5; }",
23557            "second.rs": "// Test file",
23558        }),
23559    )
23560    .await;
23561
23562    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23563    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23564    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23565
23566    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23567    language_registry.add(rust_lang());
23568    let mut fake_servers = language_registry.register_fake_lsp(
23569        "Rust",
23570        FakeLspAdapter {
23571            capabilities: lsp::ServerCapabilities {
23572                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23573                    lsp::DiagnosticOptions {
23574                        identifier: None,
23575                        inter_file_dependencies: true,
23576                        workspace_diagnostics: true,
23577                        work_done_progress_options: Default::default(),
23578                    },
23579                )),
23580                ..Default::default()
23581            },
23582            ..Default::default()
23583        },
23584    );
23585
23586    let editor = workspace
23587        .update(cx, |workspace, window, cx| {
23588            workspace.open_abs_path(
23589                PathBuf::from(path!("/a/first.rs")),
23590                OpenOptions::default(),
23591                window,
23592                cx,
23593            )
23594        })
23595        .unwrap()
23596        .await
23597        .unwrap()
23598        .downcast::<Editor>()
23599        .unwrap();
23600    let fake_server = fake_servers.next().await.unwrap();
23601    let server_id = fake_server.server.server_id();
23602    let mut first_request = fake_server
23603        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23604            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23605            let result_id = Some(new_result_id.to_string());
23606            assert_eq!(
23607                params.text_document.uri,
23608                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23609            );
23610            async move {
23611                Ok(lsp::DocumentDiagnosticReportResult::Report(
23612                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23613                        related_documents: None,
23614                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23615                            items: Vec::new(),
23616                            result_id,
23617                        },
23618                    }),
23619                ))
23620            }
23621        });
23622
23623    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23624        project.update(cx, |project, cx| {
23625            let buffer_id = editor
23626                .read(cx)
23627                .buffer()
23628                .read(cx)
23629                .as_singleton()
23630                .expect("created a singleton buffer")
23631                .read(cx)
23632                .remote_id();
23633            let buffer_result_id = project
23634                .lsp_store()
23635                .read(cx)
23636                .result_id(server_id, buffer_id, cx);
23637            assert_eq!(expected, buffer_result_id);
23638        });
23639    };
23640
23641    ensure_result_id(None, cx);
23642    cx.executor().advance_clock(Duration::from_millis(60));
23643    cx.executor().run_until_parked();
23644    assert_eq!(
23645        diagnostic_requests.load(atomic::Ordering::Acquire),
23646        1,
23647        "Opening file should trigger diagnostic request"
23648    );
23649    first_request
23650        .next()
23651        .await
23652        .expect("should have sent the first diagnostics pull request");
23653    ensure_result_id(Some("1".to_string()), cx);
23654
23655    // Editing should trigger diagnostics
23656    editor.update_in(cx, |editor, window, cx| {
23657        editor.handle_input("2", window, cx)
23658    });
23659    cx.executor().advance_clock(Duration::from_millis(60));
23660    cx.executor().run_until_parked();
23661    assert_eq!(
23662        diagnostic_requests.load(atomic::Ordering::Acquire),
23663        2,
23664        "Editing should trigger diagnostic request"
23665    );
23666    ensure_result_id(Some("2".to_string()), cx);
23667
23668    // Moving cursor should not trigger diagnostic request
23669    editor.update_in(cx, |editor, window, cx| {
23670        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23671            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23672        });
23673    });
23674    cx.executor().advance_clock(Duration::from_millis(60));
23675    cx.executor().run_until_parked();
23676    assert_eq!(
23677        diagnostic_requests.load(atomic::Ordering::Acquire),
23678        2,
23679        "Cursor movement should not trigger diagnostic request"
23680    );
23681    ensure_result_id(Some("2".to_string()), cx);
23682    // Multiple rapid edits should be debounced
23683    for _ in 0..5 {
23684        editor.update_in(cx, |editor, window, cx| {
23685            editor.handle_input("x", window, cx)
23686        });
23687    }
23688    cx.executor().advance_clock(Duration::from_millis(60));
23689    cx.executor().run_until_parked();
23690
23691    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23692    assert!(
23693        final_requests <= 4,
23694        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23695    );
23696    ensure_result_id(Some(final_requests.to_string()), cx);
23697}
23698
23699#[gpui::test]
23700async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23701    // Regression test for issue #11671
23702    // Previously, adding a cursor after moving multiple cursors would reset
23703    // the cursor count instead of adding to the existing cursors.
23704    init_test(cx, |_| {});
23705    let mut cx = EditorTestContext::new(cx).await;
23706
23707    // Create a simple buffer with cursor at start
23708    cx.set_state(indoc! {"
23709        ˇaaaa
23710        bbbb
23711        cccc
23712        dddd
23713        eeee
23714        ffff
23715        gggg
23716        hhhh"});
23717
23718    // Add 2 cursors below (so we have 3 total)
23719    cx.update_editor(|editor, window, cx| {
23720        editor.add_selection_below(&Default::default(), window, cx);
23721        editor.add_selection_below(&Default::default(), window, cx);
23722    });
23723
23724    // Verify we have 3 cursors
23725    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23726    assert_eq!(
23727        initial_count, 3,
23728        "Should have 3 cursors after adding 2 below"
23729    );
23730
23731    // Move down one line
23732    cx.update_editor(|editor, window, cx| {
23733        editor.move_down(&MoveDown, window, cx);
23734    });
23735
23736    // Add another cursor below
23737    cx.update_editor(|editor, window, cx| {
23738        editor.add_selection_below(&Default::default(), window, cx);
23739    });
23740
23741    // Should now have 4 cursors (3 original + 1 new)
23742    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23743    assert_eq!(
23744        final_count, 4,
23745        "Should have 4 cursors after moving and adding another"
23746    );
23747}
23748
23749#[gpui::test(iterations = 10)]
23750async fn test_document_colors(cx: &mut TestAppContext) {
23751    let expected_color = Rgba {
23752        r: 0.33,
23753        g: 0.33,
23754        b: 0.33,
23755        a: 0.33,
23756    };
23757
23758    init_test(cx, |_| {});
23759
23760    let fs = FakeFs::new(cx.executor());
23761    fs.insert_tree(
23762        path!("/a"),
23763        json!({
23764            "first.rs": "fn main() { let a = 5; }",
23765        }),
23766    )
23767    .await;
23768
23769    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23770    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23771    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23772
23773    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23774    language_registry.add(rust_lang());
23775    let mut fake_servers = language_registry.register_fake_lsp(
23776        "Rust",
23777        FakeLspAdapter {
23778            capabilities: lsp::ServerCapabilities {
23779                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23780                ..lsp::ServerCapabilities::default()
23781            },
23782            name: "rust-analyzer",
23783            ..FakeLspAdapter::default()
23784        },
23785    );
23786    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23787        "Rust",
23788        FakeLspAdapter {
23789            capabilities: lsp::ServerCapabilities {
23790                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23791                ..lsp::ServerCapabilities::default()
23792            },
23793            name: "not-rust-analyzer",
23794            ..FakeLspAdapter::default()
23795        },
23796    );
23797
23798    let editor = workspace
23799        .update(cx, |workspace, window, cx| {
23800            workspace.open_abs_path(
23801                PathBuf::from(path!("/a/first.rs")),
23802                OpenOptions::default(),
23803                window,
23804                cx,
23805            )
23806        })
23807        .unwrap()
23808        .await
23809        .unwrap()
23810        .downcast::<Editor>()
23811        .unwrap();
23812    let fake_language_server = fake_servers.next().await.unwrap();
23813    let fake_language_server_without_capabilities =
23814        fake_servers_without_capabilities.next().await.unwrap();
23815    let requests_made = Arc::new(AtomicUsize::new(0));
23816    let closure_requests_made = Arc::clone(&requests_made);
23817    let mut color_request_handle = fake_language_server
23818        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23819            let requests_made = Arc::clone(&closure_requests_made);
23820            async move {
23821                assert_eq!(
23822                    params.text_document.uri,
23823                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23824                );
23825                requests_made.fetch_add(1, atomic::Ordering::Release);
23826                Ok(vec![
23827                    lsp::ColorInformation {
23828                        range: lsp::Range {
23829                            start: lsp::Position {
23830                                line: 0,
23831                                character: 0,
23832                            },
23833                            end: lsp::Position {
23834                                line: 0,
23835                                character: 1,
23836                            },
23837                        },
23838                        color: lsp::Color {
23839                            red: 0.33,
23840                            green: 0.33,
23841                            blue: 0.33,
23842                            alpha: 0.33,
23843                        },
23844                    },
23845                    lsp::ColorInformation {
23846                        range: lsp::Range {
23847                            start: lsp::Position {
23848                                line: 0,
23849                                character: 0,
23850                            },
23851                            end: lsp::Position {
23852                                line: 0,
23853                                character: 1,
23854                            },
23855                        },
23856                        color: lsp::Color {
23857                            red: 0.33,
23858                            green: 0.33,
23859                            blue: 0.33,
23860                            alpha: 0.33,
23861                        },
23862                    },
23863                ])
23864            }
23865        });
23866
23867    let _handle = fake_language_server_without_capabilities
23868        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23869            panic!("Should not be called");
23870        });
23871    cx.executor().advance_clock(Duration::from_millis(100));
23872    color_request_handle.next().await.unwrap();
23873    cx.run_until_parked();
23874    assert_eq!(
23875        1,
23876        requests_made.load(atomic::Ordering::Acquire),
23877        "Should query for colors once per editor open"
23878    );
23879    editor.update_in(cx, |editor, _, cx| {
23880        assert_eq!(
23881            vec![expected_color],
23882            extract_color_inlays(editor, cx),
23883            "Should have an initial inlay"
23884        );
23885    });
23886
23887    // opening another file in a split should not influence the LSP query counter
23888    workspace
23889        .update(cx, |workspace, window, cx| {
23890            assert_eq!(
23891                workspace.panes().len(),
23892                1,
23893                "Should have one pane with one editor"
23894            );
23895            workspace.move_item_to_pane_in_direction(
23896                &MoveItemToPaneInDirection {
23897                    direction: SplitDirection::Right,
23898                    focus: false,
23899                    clone: true,
23900                },
23901                window,
23902                cx,
23903            );
23904        })
23905        .unwrap();
23906    cx.run_until_parked();
23907    workspace
23908        .update(cx, |workspace, _, cx| {
23909            let panes = workspace.panes();
23910            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23911            for pane in panes {
23912                let editor = pane
23913                    .read(cx)
23914                    .active_item()
23915                    .and_then(|item| item.downcast::<Editor>())
23916                    .expect("Should have opened an editor in each split");
23917                let editor_file = editor
23918                    .read(cx)
23919                    .buffer()
23920                    .read(cx)
23921                    .as_singleton()
23922                    .expect("test deals with singleton buffers")
23923                    .read(cx)
23924                    .file()
23925                    .expect("test buffese should have a file")
23926                    .path();
23927                assert_eq!(
23928                    editor_file.as_ref(),
23929                    Path::new("first.rs"),
23930                    "Both editors should be opened for the same file"
23931                )
23932            }
23933        })
23934        .unwrap();
23935
23936    cx.executor().advance_clock(Duration::from_millis(500));
23937    let save = editor.update_in(cx, |editor, window, cx| {
23938        editor.move_to_end(&MoveToEnd, window, cx);
23939        editor.handle_input("dirty", window, cx);
23940        editor.save(
23941            SaveOptions {
23942                format: true,
23943                autosave: true,
23944            },
23945            project.clone(),
23946            window,
23947            cx,
23948        )
23949    });
23950    save.await.unwrap();
23951
23952    color_request_handle.next().await.unwrap();
23953    cx.run_until_parked();
23954    assert_eq!(
23955        3,
23956        requests_made.load(atomic::Ordering::Acquire),
23957        "Should query for colors once per save and once per formatting after save"
23958    );
23959
23960    drop(editor);
23961    let close = workspace
23962        .update(cx, |workspace, window, cx| {
23963            workspace.active_pane().update(cx, |pane, cx| {
23964                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23965            })
23966        })
23967        .unwrap();
23968    close.await.unwrap();
23969    let close = workspace
23970        .update(cx, |workspace, window, cx| {
23971            workspace.active_pane().update(cx, |pane, cx| {
23972                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23973            })
23974        })
23975        .unwrap();
23976    close.await.unwrap();
23977    assert_eq!(
23978        3,
23979        requests_made.load(atomic::Ordering::Acquire),
23980        "After saving and closing all editors, no extra requests should be made"
23981    );
23982    workspace
23983        .update(cx, |workspace, _, cx| {
23984            assert!(
23985                workspace.active_item(cx).is_none(),
23986                "Should close all editors"
23987            )
23988        })
23989        .unwrap();
23990
23991    workspace
23992        .update(cx, |workspace, window, cx| {
23993            workspace.active_pane().update(cx, |pane, cx| {
23994                pane.navigate_backward(window, cx);
23995            })
23996        })
23997        .unwrap();
23998    cx.executor().advance_clock(Duration::from_millis(100));
23999    cx.run_until_parked();
24000    let editor = workspace
24001        .update(cx, |workspace, _, cx| {
24002            workspace
24003                .active_item(cx)
24004                .expect("Should have reopened the editor again after navigating back")
24005                .downcast::<Editor>()
24006                .expect("Should be an editor")
24007        })
24008        .unwrap();
24009    color_request_handle.next().await.unwrap();
24010    assert_eq!(
24011        3,
24012        requests_made.load(atomic::Ordering::Acquire),
24013        "Cache should be reused on buffer close and reopen"
24014    );
24015    editor.update(cx, |editor, cx| {
24016        assert_eq!(
24017            vec![expected_color],
24018            extract_color_inlays(editor, cx),
24019            "Should have an initial inlay"
24020        );
24021    });
24022}
24023
24024#[gpui::test]
24025async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24026    init_test(cx, |_| {});
24027    let (editor, cx) = cx.add_window_view(Editor::single_line);
24028    editor.update_in(cx, |editor, window, cx| {
24029        editor.set_text("oops\n\nwow\n", window, cx)
24030    });
24031    cx.run_until_parked();
24032    editor.update(cx, |editor, cx| {
24033        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24034    });
24035    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24036    cx.run_until_parked();
24037    editor.update(cx, |editor, cx| {
24038        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24039    });
24040}
24041
24042#[track_caller]
24043fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24044    editor
24045        .all_inlays(cx)
24046        .into_iter()
24047        .filter_map(|inlay| inlay.get_color())
24048        .map(Rgba::from)
24049        .collect()
24050}