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
 8616    cx.update_buffer(|buffer, cx| {
 8617        buffer.set_language(Some(html_language), cx);
 8618    });
 8619
 8620    cx.set_state(
 8621        &r#"
 8622            <body>ˇ
 8623                <script>
 8624                    var x = 1;ˇ
 8625                </script>
 8626            </body>ˇ
 8627        "#
 8628        .unindent(),
 8629    );
 8630
 8631    // Precondition: different languages are active at different locations.
 8632    cx.update_editor(|editor, window, cx| {
 8633        let snapshot = editor.snapshot(window, cx);
 8634        let cursors = editor.selections.ranges::<usize>(cx);
 8635        let languages = cursors
 8636            .iter()
 8637            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8638            .collect::<Vec<_>>();
 8639        assert_eq!(
 8640            languages,
 8641            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8642        );
 8643    });
 8644
 8645    // Angle brackets autoclose in HTML, but not JavaScript.
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.handle_input("<", window, cx);
 8648        editor.handle_input("a", window, cx);
 8649    });
 8650    cx.assert_editor_state(
 8651        &r#"
 8652            <body><aˇ>
 8653                <script>
 8654                    var x = 1;<aˇ
 8655                </script>
 8656            </body><aˇ>
 8657        "#
 8658        .unindent(),
 8659    );
 8660
 8661    // Curly braces and parens autoclose in both HTML and JavaScript.
 8662    cx.update_editor(|editor, window, cx| {
 8663        editor.handle_input(" b=", window, cx);
 8664        editor.handle_input("{", window, cx);
 8665        editor.handle_input("c", window, cx);
 8666        editor.handle_input("(", window, cx);
 8667    });
 8668    cx.assert_editor_state(
 8669        &r#"
 8670            <body><a b={c(ˇ)}>
 8671                <script>
 8672                    var x = 1;<a b={c(ˇ)}
 8673                </script>
 8674            </body><a b={c(ˇ)}>
 8675        "#
 8676        .unindent(),
 8677    );
 8678
 8679    // Brackets that were already autoclosed are skipped.
 8680    cx.update_editor(|editor, window, cx| {
 8681        editor.handle_input(")", window, cx);
 8682        editor.handle_input("d", window, cx);
 8683        editor.handle_input("}", window, cx);
 8684    });
 8685    cx.assert_editor_state(
 8686        &r#"
 8687            <body><a b={c()d}ˇ>
 8688                <script>
 8689                    var x = 1;<a b={c()d}ˇ
 8690                </script>
 8691            </body><a b={c()d}ˇ>
 8692        "#
 8693        .unindent(),
 8694    );
 8695    cx.update_editor(|editor, window, cx| {
 8696        editor.handle_input(">", window, cx);
 8697    });
 8698    cx.assert_editor_state(
 8699        &r#"
 8700            <body><a b={c()d}>ˇ
 8701                <script>
 8702                    var x = 1;<a b={c()d}>ˇ
 8703                </script>
 8704            </body><a b={c()d}>ˇ
 8705        "#
 8706        .unindent(),
 8707    );
 8708
 8709    // Reset
 8710    cx.set_state(
 8711        &r#"
 8712            <body>ˇ
 8713                <script>
 8714                    var x = 1;ˇ
 8715                </script>
 8716            </body>ˇ
 8717        "#
 8718        .unindent(),
 8719    );
 8720
 8721    cx.update_editor(|editor, window, cx| {
 8722        editor.handle_input("<", window, cx);
 8723    });
 8724    cx.assert_editor_state(
 8725        &r#"
 8726            <body><ˇ>
 8727                <script>
 8728                    var x = 1;<ˇ
 8729                </script>
 8730            </body><ˇ>
 8731        "#
 8732        .unindent(),
 8733    );
 8734
 8735    // When backspacing, the closing angle brackets are removed.
 8736    cx.update_editor(|editor, window, cx| {
 8737        editor.backspace(&Backspace, window, cx);
 8738    });
 8739    cx.assert_editor_state(
 8740        &r#"
 8741            <body>ˇ
 8742                <script>
 8743                    var x = 1;ˇ
 8744                </script>
 8745            </body>ˇ
 8746        "#
 8747        .unindent(),
 8748    );
 8749
 8750    // Block comments autoclose in JavaScript, but not HTML.
 8751    cx.update_editor(|editor, window, cx| {
 8752        editor.handle_input("/", window, cx);
 8753        editor.handle_input("*", window, cx);
 8754    });
 8755    cx.assert_editor_state(
 8756        &r#"
 8757            <body>/*ˇ
 8758                <script>
 8759                    var x = 1;/*ˇ */
 8760                </script>
 8761            </body>/*ˇ
 8762        "#
 8763        .unindent(),
 8764    );
 8765}
 8766
 8767#[gpui::test]
 8768async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8769    init_test(cx, |_| {});
 8770
 8771    let mut cx = EditorTestContext::new(cx).await;
 8772
 8773    let rust_language = Arc::new(
 8774        Language::new(
 8775            LanguageConfig {
 8776                name: "Rust".into(),
 8777                brackets: serde_json::from_value(json!([
 8778                    { "start": "{", "end": "}", "close": true, "newline": true },
 8779                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8780                ]))
 8781                .unwrap(),
 8782                autoclose_before: "})]>".into(),
 8783                ..Default::default()
 8784            },
 8785            Some(tree_sitter_rust::LANGUAGE.into()),
 8786        )
 8787        .with_override_query("(string_literal) @string")
 8788        .unwrap(),
 8789    );
 8790
 8791    cx.language_registry().add(rust_language.clone());
 8792    cx.update_buffer(|buffer, cx| {
 8793        buffer.set_language(Some(rust_language), cx);
 8794    });
 8795
 8796    cx.set_state(
 8797        &r#"
 8798            let x = ˇ
 8799        "#
 8800        .unindent(),
 8801    );
 8802
 8803    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8804    cx.update_editor(|editor, window, cx| {
 8805        editor.handle_input("\"", window, cx);
 8806    });
 8807    cx.assert_editor_state(
 8808        &r#"
 8809            let x = "ˇ"
 8810        "#
 8811        .unindent(),
 8812    );
 8813
 8814    // Inserting another quotation mark. The cursor moves across the existing
 8815    // automatically-inserted quotation mark.
 8816    cx.update_editor(|editor, window, cx| {
 8817        editor.handle_input("\"", window, cx);
 8818    });
 8819    cx.assert_editor_state(
 8820        &r#"
 8821            let x = ""ˇ
 8822        "#
 8823        .unindent(),
 8824    );
 8825
 8826    // Reset
 8827    cx.set_state(
 8828        &r#"
 8829            let x = ˇ
 8830        "#
 8831        .unindent(),
 8832    );
 8833
 8834    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8835    cx.update_editor(|editor, window, cx| {
 8836        editor.handle_input("\"", window, cx);
 8837        editor.handle_input(" ", window, cx);
 8838        editor.move_left(&Default::default(), window, cx);
 8839        editor.handle_input("\\", window, cx);
 8840        editor.handle_input("\"", window, cx);
 8841    });
 8842    cx.assert_editor_state(
 8843        &r#"
 8844            let x = "\"ˇ "
 8845        "#
 8846        .unindent(),
 8847    );
 8848
 8849    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8850    // mark. Nothing is inserted.
 8851    cx.update_editor(|editor, window, cx| {
 8852        editor.move_right(&Default::default(), window, cx);
 8853        editor.handle_input("\"", window, cx);
 8854    });
 8855    cx.assert_editor_state(
 8856        &r#"
 8857            let x = "\" "ˇ
 8858        "#
 8859        .unindent(),
 8860    );
 8861}
 8862
 8863#[gpui::test]
 8864async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8865    init_test(cx, |_| {});
 8866
 8867    let language = Arc::new(Language::new(
 8868        LanguageConfig {
 8869            brackets: BracketPairConfig {
 8870                pairs: vec![
 8871                    BracketPair {
 8872                        start: "{".to_string(),
 8873                        end: "}".to_string(),
 8874                        close: true,
 8875                        surround: true,
 8876                        newline: true,
 8877                    },
 8878                    BracketPair {
 8879                        start: "/* ".to_string(),
 8880                        end: "*/".to_string(),
 8881                        close: true,
 8882                        surround: true,
 8883                        ..Default::default()
 8884                    },
 8885                ],
 8886                ..Default::default()
 8887            },
 8888            ..Default::default()
 8889        },
 8890        Some(tree_sitter_rust::LANGUAGE.into()),
 8891    ));
 8892
 8893    let text = r#"
 8894        a
 8895        b
 8896        c
 8897    "#
 8898    .unindent();
 8899
 8900    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8901    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8902    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8903    editor
 8904        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8905        .await;
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8909            s.select_display_ranges([
 8910                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8911                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8913            ])
 8914        });
 8915
 8916        editor.handle_input("{", window, cx);
 8917        editor.handle_input("{", window, cx);
 8918        editor.handle_input("{", window, cx);
 8919        assert_eq!(
 8920            editor.text(cx),
 8921            "
 8922                {{{a}}}
 8923                {{{b}}}
 8924                {{{c}}}
 8925            "
 8926            .unindent()
 8927        );
 8928        assert_eq!(
 8929            editor.selections.display_ranges(cx),
 8930            [
 8931                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8932                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8933                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8934            ]
 8935        );
 8936
 8937        editor.undo(&Undo, window, cx);
 8938        editor.undo(&Undo, window, cx);
 8939        editor.undo(&Undo, window, cx);
 8940        assert_eq!(
 8941            editor.text(cx),
 8942            "
 8943                a
 8944                b
 8945                c
 8946            "
 8947            .unindent()
 8948        );
 8949        assert_eq!(
 8950            editor.selections.display_ranges(cx),
 8951            [
 8952                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8953                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8954                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8955            ]
 8956        );
 8957
 8958        // Ensure inserting the first character of a multi-byte bracket pair
 8959        // doesn't surround the selections with the bracket.
 8960        editor.handle_input("/", window, cx);
 8961        assert_eq!(
 8962            editor.text(cx),
 8963            "
 8964                /
 8965                /
 8966                /
 8967            "
 8968            .unindent()
 8969        );
 8970        assert_eq!(
 8971            editor.selections.display_ranges(cx),
 8972            [
 8973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8974                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8975                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8976            ]
 8977        );
 8978
 8979        editor.undo(&Undo, window, cx);
 8980        assert_eq!(
 8981            editor.text(cx),
 8982            "
 8983                a
 8984                b
 8985                c
 8986            "
 8987            .unindent()
 8988        );
 8989        assert_eq!(
 8990            editor.selections.display_ranges(cx),
 8991            [
 8992                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8993                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8994                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8995            ]
 8996        );
 8997
 8998        // Ensure inserting the last character of a multi-byte bracket pair
 8999        // doesn't surround the selections with the bracket.
 9000        editor.handle_input("*", window, cx);
 9001        assert_eq!(
 9002            editor.text(cx),
 9003            "
 9004                *
 9005                *
 9006                *
 9007            "
 9008            .unindent()
 9009        );
 9010        assert_eq!(
 9011            editor.selections.display_ranges(cx),
 9012            [
 9013                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9014                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9015                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9016            ]
 9017        );
 9018    });
 9019}
 9020
 9021#[gpui::test]
 9022async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9023    init_test(cx, |_| {});
 9024
 9025    let language = Arc::new(Language::new(
 9026        LanguageConfig {
 9027            brackets: BracketPairConfig {
 9028                pairs: vec![BracketPair {
 9029                    start: "{".to_string(),
 9030                    end: "}".to_string(),
 9031                    close: true,
 9032                    surround: true,
 9033                    newline: true,
 9034                }],
 9035                ..Default::default()
 9036            },
 9037            autoclose_before: "}".to_string(),
 9038            ..Default::default()
 9039        },
 9040        Some(tree_sitter_rust::LANGUAGE.into()),
 9041    ));
 9042
 9043    let text = r#"
 9044        a
 9045        b
 9046        c
 9047    "#
 9048    .unindent();
 9049
 9050    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9051    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9052    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9053    editor
 9054        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9055        .await;
 9056
 9057    editor.update_in(cx, |editor, window, cx| {
 9058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9059            s.select_ranges([
 9060                Point::new(0, 1)..Point::new(0, 1),
 9061                Point::new(1, 1)..Point::new(1, 1),
 9062                Point::new(2, 1)..Point::new(2, 1),
 9063            ])
 9064        });
 9065
 9066        editor.handle_input("{", window, cx);
 9067        editor.handle_input("{", window, cx);
 9068        editor.handle_input("_", window, cx);
 9069        assert_eq!(
 9070            editor.text(cx),
 9071            "
 9072                a{{_}}
 9073                b{{_}}
 9074                c{{_}}
 9075            "
 9076            .unindent()
 9077        );
 9078        assert_eq!(
 9079            editor.selections.ranges::<Point>(cx),
 9080            [
 9081                Point::new(0, 4)..Point::new(0, 4),
 9082                Point::new(1, 4)..Point::new(1, 4),
 9083                Point::new(2, 4)..Point::new(2, 4)
 9084            ]
 9085        );
 9086
 9087        editor.backspace(&Default::default(), window, cx);
 9088        editor.backspace(&Default::default(), window, cx);
 9089        assert_eq!(
 9090            editor.text(cx),
 9091            "
 9092                a{}
 9093                b{}
 9094                c{}
 9095            "
 9096            .unindent()
 9097        );
 9098        assert_eq!(
 9099            editor.selections.ranges::<Point>(cx),
 9100            [
 9101                Point::new(0, 2)..Point::new(0, 2),
 9102                Point::new(1, 2)..Point::new(1, 2),
 9103                Point::new(2, 2)..Point::new(2, 2)
 9104            ]
 9105        );
 9106
 9107        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9108        assert_eq!(
 9109            editor.text(cx),
 9110            "
 9111                a
 9112                b
 9113                c
 9114            "
 9115            .unindent()
 9116        );
 9117        assert_eq!(
 9118            editor.selections.ranges::<Point>(cx),
 9119            [
 9120                Point::new(0, 1)..Point::new(0, 1),
 9121                Point::new(1, 1)..Point::new(1, 1),
 9122                Point::new(2, 1)..Point::new(2, 1)
 9123            ]
 9124        );
 9125    });
 9126}
 9127
 9128#[gpui::test]
 9129async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9130    init_test(cx, |settings| {
 9131        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9132    });
 9133
 9134    let mut cx = EditorTestContext::new(cx).await;
 9135
 9136    let language = Arc::new(Language::new(
 9137        LanguageConfig {
 9138            brackets: BracketPairConfig {
 9139                pairs: vec![
 9140                    BracketPair {
 9141                        start: "{".to_string(),
 9142                        end: "}".to_string(),
 9143                        close: true,
 9144                        surround: true,
 9145                        newline: true,
 9146                    },
 9147                    BracketPair {
 9148                        start: "(".to_string(),
 9149                        end: ")".to_string(),
 9150                        close: true,
 9151                        surround: true,
 9152                        newline: true,
 9153                    },
 9154                    BracketPair {
 9155                        start: "[".to_string(),
 9156                        end: "]".to_string(),
 9157                        close: false,
 9158                        surround: true,
 9159                        newline: true,
 9160                    },
 9161                ],
 9162                ..Default::default()
 9163            },
 9164            autoclose_before: "})]".to_string(),
 9165            ..Default::default()
 9166        },
 9167        Some(tree_sitter_rust::LANGUAGE.into()),
 9168    ));
 9169
 9170    cx.language_registry().add(language.clone());
 9171    cx.update_buffer(|buffer, cx| {
 9172        buffer.set_language(Some(language), cx);
 9173    });
 9174
 9175    cx.set_state(
 9176        &"
 9177            {(ˇ)}
 9178            [[ˇ]]
 9179            {(ˇ)}
 9180        "
 9181        .unindent(),
 9182    );
 9183
 9184    cx.update_editor(|editor, window, cx| {
 9185        editor.backspace(&Default::default(), window, cx);
 9186        editor.backspace(&Default::default(), window, cx);
 9187    });
 9188
 9189    cx.assert_editor_state(
 9190        &"
 9191            ˇ
 9192            ˇ]]
 9193            ˇ
 9194        "
 9195        .unindent(),
 9196    );
 9197
 9198    cx.update_editor(|editor, window, cx| {
 9199        editor.handle_input("{", window, cx);
 9200        editor.handle_input("{", window, cx);
 9201        editor.move_right(&MoveRight, window, cx);
 9202        editor.move_right(&MoveRight, window, cx);
 9203        editor.move_left(&MoveLeft, window, cx);
 9204        editor.move_left(&MoveLeft, window, cx);
 9205        editor.backspace(&Default::default(), window, cx);
 9206    });
 9207
 9208    cx.assert_editor_state(
 9209        &"
 9210            {ˇ}
 9211            {ˇ}]]
 9212            {ˇ}
 9213        "
 9214        .unindent(),
 9215    );
 9216
 9217    cx.update_editor(|editor, window, cx| {
 9218        editor.backspace(&Default::default(), window, cx);
 9219    });
 9220
 9221    cx.assert_editor_state(
 9222        &"
 9223            ˇ
 9224            ˇ]]
 9225            ˇ
 9226        "
 9227        .unindent(),
 9228    );
 9229}
 9230
 9231#[gpui::test]
 9232async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9233    init_test(cx, |_| {});
 9234
 9235    let language = Arc::new(Language::new(
 9236        LanguageConfig::default(),
 9237        Some(tree_sitter_rust::LANGUAGE.into()),
 9238    ));
 9239
 9240    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9241    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9242    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9243    editor
 9244        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9245        .await;
 9246
 9247    editor.update_in(cx, |editor, window, cx| {
 9248        editor.set_auto_replace_emoji_shortcode(true);
 9249
 9250        editor.handle_input("Hello ", window, cx);
 9251        editor.handle_input(":wave", window, cx);
 9252        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9253
 9254        editor.handle_input(":", window, cx);
 9255        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9256
 9257        editor.handle_input(" :smile", window, cx);
 9258        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9259
 9260        editor.handle_input(":", window, cx);
 9261        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9262
 9263        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9264        editor.handle_input(":wave", window, cx);
 9265        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9266
 9267        editor.handle_input(":", window, cx);
 9268        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9269
 9270        editor.handle_input(":1", window, cx);
 9271        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9272
 9273        editor.handle_input(":", window, cx);
 9274        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9275
 9276        // Ensure shortcode does not get replaced when it is part of a word
 9277        editor.handle_input(" Test:wave", window, cx);
 9278        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9279
 9280        editor.handle_input(":", window, cx);
 9281        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9282
 9283        editor.set_auto_replace_emoji_shortcode(false);
 9284
 9285        // Ensure shortcode does not get replaced when auto replace is off
 9286        editor.handle_input(" :wave", window, cx);
 9287        assert_eq!(
 9288            editor.text(cx),
 9289            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9290        );
 9291
 9292        editor.handle_input(":", window, cx);
 9293        assert_eq!(
 9294            editor.text(cx),
 9295            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9296        );
 9297    });
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    let (text, insertion_ranges) = marked_text_ranges(
 9305        indoc! {"
 9306            ˇ
 9307        "},
 9308        false,
 9309    );
 9310
 9311    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9312    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9313
 9314    _ = editor.update_in(cx, |editor, window, cx| {
 9315        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9316
 9317        editor
 9318            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9319            .unwrap();
 9320
 9321        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9322            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9323            assert_eq!(editor.text(cx), expected_text);
 9324            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9325        }
 9326
 9327        assert(
 9328            editor,
 9329            cx,
 9330            indoc! {"
 9331            type «» =•
 9332            "},
 9333        );
 9334
 9335        assert!(editor.context_menu_visible(), "There should be a matches");
 9336    });
 9337}
 9338
 9339#[gpui::test]
 9340async fn test_snippets(cx: &mut TestAppContext) {
 9341    init_test(cx, |_| {});
 9342
 9343    let mut cx = EditorTestContext::new(cx).await;
 9344
 9345    cx.set_state(indoc! {"
 9346        a.ˇ b
 9347        a.ˇ b
 9348        a.ˇ b
 9349    "});
 9350
 9351    cx.update_editor(|editor, window, cx| {
 9352        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9353        let insertion_ranges = editor
 9354            .selections
 9355            .all(cx)
 9356            .iter()
 9357            .map(|s| s.range().clone())
 9358            .collect::<Vec<_>>();
 9359        editor
 9360            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9361            .unwrap();
 9362    });
 9363
 9364    cx.assert_editor_state(indoc! {"
 9365        a.f(«oneˇ», two, «threeˇ») b
 9366        a.f(«oneˇ», two, «threeˇ») b
 9367        a.f(«oneˇ», two, «threeˇ») b
 9368    "});
 9369
 9370    // Can't move earlier than the first tab stop
 9371    cx.update_editor(|editor, window, cx| {
 9372        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9373    });
 9374    cx.assert_editor_state(indoc! {"
 9375        a.f(«oneˇ», two, «threeˇ») b
 9376        a.f(«oneˇ», two, «threeˇ») b
 9377        a.f(«oneˇ», two, «threeˇ») b
 9378    "});
 9379
 9380    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9381    cx.assert_editor_state(indoc! {"
 9382        a.f(one, «twoˇ», three) b
 9383        a.f(one, «twoˇ», three) b
 9384        a.f(one, «twoˇ», three) b
 9385    "});
 9386
 9387    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9388    cx.assert_editor_state(indoc! {"
 9389        a.f(«oneˇ», two, «threeˇ») b
 9390        a.f(«oneˇ», two, «threeˇ») b
 9391        a.f(«oneˇ», two, «threeˇ») b
 9392    "});
 9393
 9394    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9395    cx.assert_editor_state(indoc! {"
 9396        a.f(one, «twoˇ», three) b
 9397        a.f(one, «twoˇ», three) b
 9398        a.f(one, «twoˇ», three) b
 9399    "});
 9400    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9401    cx.assert_editor_state(indoc! {"
 9402        a.f(one, two, three)ˇ b
 9403        a.f(one, two, three)ˇ b
 9404        a.f(one, two, three)ˇ b
 9405    "});
 9406
 9407    // As soon as the last tab stop is reached, snippet state is gone
 9408    cx.update_editor(|editor, window, cx| {
 9409        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9410    });
 9411    cx.assert_editor_state(indoc! {"
 9412        a.f(one, two, three)ˇ b
 9413        a.f(one, two, three)ˇ b
 9414        a.f(one, two, three)ˇ b
 9415    "});
 9416}
 9417
 9418#[gpui::test]
 9419async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9420    init_test(cx, |_| {});
 9421
 9422    let mut cx = EditorTestContext::new(cx).await;
 9423
 9424    cx.update_editor(|editor, window, cx| {
 9425        let snippet = Snippet::parse(indoc! {"
 9426            /*
 9427             * Multiline comment with leading indentation
 9428             *
 9429             * $1
 9430             */
 9431            $0"})
 9432        .unwrap();
 9433        let insertion_ranges = editor
 9434            .selections
 9435            .all(cx)
 9436            .iter()
 9437            .map(|s| s.range().clone())
 9438            .collect::<Vec<_>>();
 9439        editor
 9440            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9441            .unwrap();
 9442    });
 9443
 9444    cx.assert_editor_state(indoc! {"
 9445        /*
 9446         * Multiline comment with leading indentation
 9447         *
 9448         * ˇ
 9449         */
 9450    "});
 9451
 9452    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9453    cx.assert_editor_state(indoc! {"
 9454        /*
 9455         * Multiline comment with leading indentation
 9456         *
 9457         *•
 9458         */
 9459        ˇ"});
 9460}
 9461
 9462#[gpui::test]
 9463async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9464    init_test(cx, |_| {});
 9465
 9466    let fs = FakeFs::new(cx.executor());
 9467    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9468
 9469    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9470
 9471    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9472    language_registry.add(rust_lang());
 9473    let mut fake_servers = language_registry.register_fake_lsp(
 9474        "Rust",
 9475        FakeLspAdapter {
 9476            capabilities: lsp::ServerCapabilities {
 9477                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9478                ..Default::default()
 9479            },
 9480            ..Default::default()
 9481        },
 9482    );
 9483
 9484    let buffer = project
 9485        .update(cx, |project, cx| {
 9486            project.open_local_buffer(path!("/file.rs"), cx)
 9487        })
 9488        .await
 9489        .unwrap();
 9490
 9491    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9492    let (editor, cx) = cx.add_window_view(|window, cx| {
 9493        build_editor_with_project(project.clone(), buffer, window, cx)
 9494    });
 9495    editor.update_in(cx, |editor, window, cx| {
 9496        editor.set_text("one\ntwo\nthree\n", window, cx)
 9497    });
 9498    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9499
 9500    cx.executor().start_waiting();
 9501    let fake_server = fake_servers.next().await.unwrap();
 9502
 9503    {
 9504        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9505            move |params, _| async move {
 9506                assert_eq!(
 9507                    params.text_document.uri,
 9508                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9509                );
 9510                assert_eq!(params.options.tab_size, 4);
 9511                Ok(Some(vec![lsp::TextEdit::new(
 9512                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9513                    ", ".to_string(),
 9514                )]))
 9515            },
 9516        );
 9517        let save = editor
 9518            .update_in(cx, |editor, window, cx| {
 9519                editor.save(
 9520                    SaveOptions {
 9521                        format: true,
 9522                        autosave: false,
 9523                    },
 9524                    project.clone(),
 9525                    window,
 9526                    cx,
 9527                )
 9528            })
 9529            .unwrap();
 9530        cx.executor().start_waiting();
 9531        save.await;
 9532
 9533        assert_eq!(
 9534            editor.update(cx, |editor, cx| editor.text(cx)),
 9535            "one, two\nthree\n"
 9536        );
 9537        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9538    }
 9539
 9540    {
 9541        editor.update_in(cx, |editor, window, cx| {
 9542            editor.set_text("one\ntwo\nthree\n", window, cx)
 9543        });
 9544        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9545
 9546        // Ensure we can still save even if formatting hangs.
 9547        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9548            move |params, _| async move {
 9549                assert_eq!(
 9550                    params.text_document.uri,
 9551                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9552                );
 9553                futures::future::pending::<()>().await;
 9554                unreachable!()
 9555            },
 9556        );
 9557        let save = editor
 9558            .update_in(cx, |editor, window, cx| {
 9559                editor.save(
 9560                    SaveOptions {
 9561                        format: true,
 9562                        autosave: false,
 9563                    },
 9564                    project.clone(),
 9565                    window,
 9566                    cx,
 9567                )
 9568            })
 9569            .unwrap();
 9570        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9571        cx.executor().start_waiting();
 9572        save.await;
 9573        assert_eq!(
 9574            editor.update(cx, |editor, cx| editor.text(cx)),
 9575            "one\ntwo\nthree\n"
 9576        );
 9577    }
 9578
 9579    // Set rust language override and assert overridden tabsize is sent to language server
 9580    update_test_language_settings(cx, |settings| {
 9581        settings.languages.0.insert(
 9582            "Rust".into(),
 9583            LanguageSettingsContent {
 9584                tab_size: NonZeroU32::new(8),
 9585                ..Default::default()
 9586            },
 9587        );
 9588    });
 9589
 9590    {
 9591        editor.update_in(cx, |editor, window, cx| {
 9592            editor.set_text("somehting_new\n", window, cx)
 9593        });
 9594        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9595        let _formatting_request_signal = fake_server
 9596            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9597                assert_eq!(
 9598                    params.text_document.uri,
 9599                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9600                );
 9601                assert_eq!(params.options.tab_size, 8);
 9602                Ok(Some(vec![]))
 9603            });
 9604        let save = editor
 9605            .update_in(cx, |editor, window, cx| {
 9606                editor.save(
 9607                    SaveOptions {
 9608                        format: true,
 9609                        autosave: false,
 9610                    },
 9611                    project.clone(),
 9612                    window,
 9613                    cx,
 9614                )
 9615            })
 9616            .unwrap();
 9617        cx.executor().start_waiting();
 9618        save.await;
 9619    }
 9620}
 9621
 9622#[gpui::test]
 9623async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9624    init_test(cx, |settings| {
 9625        settings.defaults.ensure_final_newline_on_save = Some(false);
 9626    });
 9627
 9628    let fs = FakeFs::new(cx.executor());
 9629    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9630
 9631    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9632
 9633    let buffer = project
 9634        .update(cx, |project, cx| {
 9635            project.open_local_buffer(path!("/file.txt"), cx)
 9636        })
 9637        .await
 9638        .unwrap();
 9639
 9640    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9641    let (editor, cx) = cx.add_window_view(|window, cx| {
 9642        build_editor_with_project(project.clone(), buffer, window, cx)
 9643    });
 9644    editor.update_in(cx, |editor, window, cx| {
 9645        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9646            s.select_ranges([0..0])
 9647        });
 9648    });
 9649    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9650
 9651    editor.update_in(cx, |editor, window, cx| {
 9652        editor.handle_input("\n", window, cx)
 9653    });
 9654    cx.run_until_parked();
 9655    save(&editor, &project, cx).await;
 9656    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9657
 9658    editor.update_in(cx, |editor, window, cx| {
 9659        editor.undo(&Default::default(), window, cx);
 9660    });
 9661    save(&editor, &project, cx).await;
 9662    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9663
 9664    editor.update_in(cx, |editor, window, cx| {
 9665        editor.redo(&Default::default(), window, cx);
 9666    });
 9667    cx.run_until_parked();
 9668    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9669
 9670    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9671        let save = editor
 9672            .update_in(cx, |editor, window, cx| {
 9673                editor.save(
 9674                    SaveOptions {
 9675                        format: true,
 9676                        autosave: false,
 9677                    },
 9678                    project.clone(),
 9679                    window,
 9680                    cx,
 9681                )
 9682            })
 9683            .unwrap();
 9684        cx.executor().start_waiting();
 9685        save.await;
 9686        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9687    }
 9688}
 9689
 9690#[gpui::test]
 9691async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9692    init_test(cx, |_| {});
 9693
 9694    let cols = 4;
 9695    let rows = 10;
 9696    let sample_text_1 = sample_text(rows, cols, 'a');
 9697    assert_eq!(
 9698        sample_text_1,
 9699        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9700    );
 9701    let sample_text_2 = sample_text(rows, cols, 'l');
 9702    assert_eq!(
 9703        sample_text_2,
 9704        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9705    );
 9706    let sample_text_3 = sample_text(rows, cols, 'v');
 9707    assert_eq!(
 9708        sample_text_3,
 9709        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9710    );
 9711
 9712    let fs = FakeFs::new(cx.executor());
 9713    fs.insert_tree(
 9714        path!("/a"),
 9715        json!({
 9716            "main.rs": sample_text_1,
 9717            "other.rs": sample_text_2,
 9718            "lib.rs": sample_text_3,
 9719        }),
 9720    )
 9721    .await;
 9722
 9723    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9725    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9726
 9727    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9728    language_registry.add(rust_lang());
 9729    let mut fake_servers = language_registry.register_fake_lsp(
 9730        "Rust",
 9731        FakeLspAdapter {
 9732            capabilities: lsp::ServerCapabilities {
 9733                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9734                ..Default::default()
 9735            },
 9736            ..Default::default()
 9737        },
 9738    );
 9739
 9740    let worktree = project.update(cx, |project, cx| {
 9741        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9742        assert_eq!(worktrees.len(), 1);
 9743        worktrees.pop().unwrap()
 9744    });
 9745    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9746
 9747    let buffer_1 = project
 9748        .update(cx, |project, cx| {
 9749            project.open_buffer((worktree_id, "main.rs"), cx)
 9750        })
 9751        .await
 9752        .unwrap();
 9753    let buffer_2 = project
 9754        .update(cx, |project, cx| {
 9755            project.open_buffer((worktree_id, "other.rs"), cx)
 9756        })
 9757        .await
 9758        .unwrap();
 9759    let buffer_3 = project
 9760        .update(cx, |project, cx| {
 9761            project.open_buffer((worktree_id, "lib.rs"), cx)
 9762        })
 9763        .await
 9764        .unwrap();
 9765
 9766    let multi_buffer = cx.new(|cx| {
 9767        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9768        multi_buffer.push_excerpts(
 9769            buffer_1.clone(),
 9770            [
 9771                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9772                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9773                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9774            ],
 9775            cx,
 9776        );
 9777        multi_buffer.push_excerpts(
 9778            buffer_2.clone(),
 9779            [
 9780                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9781                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9782                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9783            ],
 9784            cx,
 9785        );
 9786        multi_buffer.push_excerpts(
 9787            buffer_3.clone(),
 9788            [
 9789                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9790                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9791                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9792            ],
 9793            cx,
 9794        );
 9795        multi_buffer
 9796    });
 9797    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9798        Editor::new(
 9799            EditorMode::full(),
 9800            multi_buffer,
 9801            Some(project.clone()),
 9802            window,
 9803            cx,
 9804        )
 9805    });
 9806
 9807    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9808        editor.change_selections(
 9809            SelectionEffects::scroll(Autoscroll::Next),
 9810            window,
 9811            cx,
 9812            |s| s.select_ranges(Some(1..2)),
 9813        );
 9814        editor.insert("|one|two|three|", window, cx);
 9815    });
 9816    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9817    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9818        editor.change_selections(
 9819            SelectionEffects::scroll(Autoscroll::Next),
 9820            window,
 9821            cx,
 9822            |s| s.select_ranges(Some(60..70)),
 9823        );
 9824        editor.insert("|four|five|six|", window, cx);
 9825    });
 9826    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9827
 9828    // First two buffers should be edited, but not the third one.
 9829    assert_eq!(
 9830        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9831        "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}",
 9832    );
 9833    buffer_1.update(cx, |buffer, _| {
 9834        assert!(buffer.is_dirty());
 9835        assert_eq!(
 9836            buffer.text(),
 9837            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9838        )
 9839    });
 9840    buffer_2.update(cx, |buffer, _| {
 9841        assert!(buffer.is_dirty());
 9842        assert_eq!(
 9843            buffer.text(),
 9844            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9845        )
 9846    });
 9847    buffer_3.update(cx, |buffer, _| {
 9848        assert!(!buffer.is_dirty());
 9849        assert_eq!(buffer.text(), sample_text_3,)
 9850    });
 9851    cx.executor().run_until_parked();
 9852
 9853    cx.executor().start_waiting();
 9854    let save = multi_buffer_editor
 9855        .update_in(cx, |editor, window, cx| {
 9856            editor.save(
 9857                SaveOptions {
 9858                    format: true,
 9859                    autosave: false,
 9860                },
 9861                project.clone(),
 9862                window,
 9863                cx,
 9864            )
 9865        })
 9866        .unwrap();
 9867
 9868    let fake_server = fake_servers.next().await.unwrap();
 9869    fake_server
 9870        .server
 9871        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9872            Ok(Some(vec![lsp::TextEdit::new(
 9873                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9874                format!("[{} formatted]", params.text_document.uri),
 9875            )]))
 9876        })
 9877        .detach();
 9878    save.await;
 9879
 9880    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9881    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9882    assert_eq!(
 9883        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9884        uri!(
 9885            "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}"
 9886        ),
 9887    );
 9888    buffer_1.update(cx, |buffer, _| {
 9889        assert!(!buffer.is_dirty());
 9890        assert_eq!(
 9891            buffer.text(),
 9892            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9893        )
 9894    });
 9895    buffer_2.update(cx, |buffer, _| {
 9896        assert!(!buffer.is_dirty());
 9897        assert_eq!(
 9898            buffer.text(),
 9899            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9900        )
 9901    });
 9902    buffer_3.update(cx, |buffer, _| {
 9903        assert!(!buffer.is_dirty());
 9904        assert_eq!(buffer.text(), sample_text_3,)
 9905    });
 9906}
 9907
 9908#[gpui::test]
 9909async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9910    init_test(cx, |_| {});
 9911
 9912    let fs = FakeFs::new(cx.executor());
 9913    fs.insert_tree(
 9914        path!("/dir"),
 9915        json!({
 9916            "file1.rs": "fn main() { println!(\"hello\"); }",
 9917            "file2.rs": "fn test() { println!(\"test\"); }",
 9918            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9919        }),
 9920    )
 9921    .await;
 9922
 9923    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9924    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9925    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9926
 9927    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9928    language_registry.add(rust_lang());
 9929
 9930    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9931    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9932
 9933    // Open three buffers
 9934    let buffer_1 = project
 9935        .update(cx, |project, cx| {
 9936            project.open_buffer((worktree_id, "file1.rs"), cx)
 9937        })
 9938        .await
 9939        .unwrap();
 9940    let buffer_2 = project
 9941        .update(cx, |project, cx| {
 9942            project.open_buffer((worktree_id, "file2.rs"), cx)
 9943        })
 9944        .await
 9945        .unwrap();
 9946    let buffer_3 = project
 9947        .update(cx, |project, cx| {
 9948            project.open_buffer((worktree_id, "file3.rs"), cx)
 9949        })
 9950        .await
 9951        .unwrap();
 9952
 9953    // Create a multi-buffer with all three buffers
 9954    let multi_buffer = cx.new(|cx| {
 9955        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9956        multi_buffer.push_excerpts(
 9957            buffer_1.clone(),
 9958            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9959            cx,
 9960        );
 9961        multi_buffer.push_excerpts(
 9962            buffer_2.clone(),
 9963            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9964            cx,
 9965        );
 9966        multi_buffer.push_excerpts(
 9967            buffer_3.clone(),
 9968            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9969            cx,
 9970        );
 9971        multi_buffer
 9972    });
 9973
 9974    let editor = cx.new_window_entity(|window, cx| {
 9975        Editor::new(
 9976            EditorMode::full(),
 9977            multi_buffer,
 9978            Some(project.clone()),
 9979            window,
 9980            cx,
 9981        )
 9982    });
 9983
 9984    // Edit only the first buffer
 9985    editor.update_in(cx, |editor, window, cx| {
 9986        editor.change_selections(
 9987            SelectionEffects::scroll(Autoscroll::Next),
 9988            window,
 9989            cx,
 9990            |s| s.select_ranges(Some(10..10)),
 9991        );
 9992        editor.insert("// edited", window, cx);
 9993    });
 9994
 9995    // Verify that only buffer 1 is dirty
 9996    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9997    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9998    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9999
10000    // Get write counts after file creation (files were created with initial content)
10001    // We expect each file to have been written once during creation
10002    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10003    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10004    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10005
10006    // Perform autosave
10007    let save_task = editor.update_in(cx, |editor, window, cx| {
10008        editor.save(
10009            SaveOptions {
10010                format: true,
10011                autosave: true,
10012            },
10013            project.clone(),
10014            window,
10015            cx,
10016        )
10017    });
10018    save_task.await.unwrap();
10019
10020    // Only the dirty buffer should have been saved
10021    assert_eq!(
10022        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10023        1,
10024        "Buffer 1 was dirty, so it should have been written once during autosave"
10025    );
10026    assert_eq!(
10027        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10028        0,
10029        "Buffer 2 was clean, so it should not have been written during autosave"
10030    );
10031    assert_eq!(
10032        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10033        0,
10034        "Buffer 3 was clean, so it should not have been written during autosave"
10035    );
10036
10037    // Verify buffer states after autosave
10038    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10039    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10040    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10041
10042    // Now perform a manual save (format = true)
10043    let save_task = editor.update_in(cx, |editor, window, cx| {
10044        editor.save(
10045            SaveOptions {
10046                format: true,
10047                autosave: false,
10048            },
10049            project.clone(),
10050            window,
10051            cx,
10052        )
10053    });
10054    save_task.await.unwrap();
10055
10056    // During manual save, clean buffers don't get written to disk
10057    // They just get did_save called for language server notifications
10058    assert_eq!(
10059        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10060        1,
10061        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10062    );
10063    assert_eq!(
10064        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10065        0,
10066        "Buffer 2 should not have been written at all"
10067    );
10068    assert_eq!(
10069        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10070        0,
10071        "Buffer 3 should not have been written at all"
10072    );
10073}
10074
10075async fn setup_range_format_test(
10076    cx: &mut TestAppContext,
10077) -> (
10078    Entity<Project>,
10079    Entity<Editor>,
10080    &mut gpui::VisualTestContext,
10081    lsp::FakeLanguageServer,
10082) {
10083    init_test(cx, |_| {});
10084
10085    let fs = FakeFs::new(cx.executor());
10086    fs.insert_file(path!("/file.rs"), Default::default()).await;
10087
10088    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10089
10090    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10091    language_registry.add(rust_lang());
10092    let mut fake_servers = language_registry.register_fake_lsp(
10093        "Rust",
10094        FakeLspAdapter {
10095            capabilities: lsp::ServerCapabilities {
10096                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10097                ..lsp::ServerCapabilities::default()
10098            },
10099            ..FakeLspAdapter::default()
10100        },
10101    );
10102
10103    let buffer = project
10104        .update(cx, |project, cx| {
10105            project.open_local_buffer(path!("/file.rs"), cx)
10106        })
10107        .await
10108        .unwrap();
10109
10110    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10111    let (editor, cx) = cx.add_window_view(|window, cx| {
10112        build_editor_with_project(project.clone(), buffer, window, cx)
10113    });
10114
10115    cx.executor().start_waiting();
10116    let fake_server = fake_servers.next().await.unwrap();
10117
10118    (project, editor, cx, fake_server)
10119}
10120
10121#[gpui::test]
10122async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10123    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10124
10125    editor.update_in(cx, |editor, window, cx| {
10126        editor.set_text("one\ntwo\nthree\n", window, cx)
10127    });
10128    assert!(cx.read(|cx| editor.is_dirty(cx)));
10129
10130    let save = editor
10131        .update_in(cx, |editor, window, cx| {
10132            editor.save(
10133                SaveOptions {
10134                    format: true,
10135                    autosave: false,
10136                },
10137                project.clone(),
10138                window,
10139                cx,
10140            )
10141        })
10142        .unwrap();
10143    fake_server
10144        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10145            assert_eq!(
10146                params.text_document.uri,
10147                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10148            );
10149            assert_eq!(params.options.tab_size, 4);
10150            Ok(Some(vec![lsp::TextEdit::new(
10151                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10152                ", ".to_string(),
10153            )]))
10154        })
10155        .next()
10156        .await;
10157    cx.executor().start_waiting();
10158    save.await;
10159    assert_eq!(
10160        editor.update(cx, |editor, cx| editor.text(cx)),
10161        "one, two\nthree\n"
10162    );
10163    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10164}
10165
10166#[gpui::test]
10167async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10168    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10169
10170    editor.update_in(cx, |editor, window, cx| {
10171        editor.set_text("one\ntwo\nthree\n", window, cx)
10172    });
10173    assert!(cx.read(|cx| editor.is_dirty(cx)));
10174
10175    // Test that save still works when formatting hangs
10176    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10177        move |params, _| async move {
10178            assert_eq!(
10179                params.text_document.uri,
10180                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10181            );
10182            futures::future::pending::<()>().await;
10183            unreachable!()
10184        },
10185    );
10186    let save = editor
10187        .update_in(cx, |editor, window, cx| {
10188            editor.save(
10189                SaveOptions {
10190                    format: true,
10191                    autosave: false,
10192                },
10193                project.clone(),
10194                window,
10195                cx,
10196            )
10197        })
10198        .unwrap();
10199    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10200    cx.executor().start_waiting();
10201    save.await;
10202    assert_eq!(
10203        editor.update(cx, |editor, cx| editor.text(cx)),
10204        "one\ntwo\nthree\n"
10205    );
10206    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10207}
10208
10209#[gpui::test]
10210async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10211    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10212
10213    // Buffer starts clean, no formatting should be requested
10214    let save = editor
10215        .update_in(cx, |editor, window, cx| {
10216            editor.save(
10217                SaveOptions {
10218                    format: false,
10219                    autosave: false,
10220                },
10221                project.clone(),
10222                window,
10223                cx,
10224            )
10225        })
10226        .unwrap();
10227    let _pending_format_request = fake_server
10228        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10229            panic!("Should not be invoked");
10230        })
10231        .next();
10232    cx.executor().start_waiting();
10233    save.await;
10234    cx.run_until_parked();
10235}
10236
10237#[gpui::test]
10238async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10239    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10240
10241    // Set Rust language override and assert overridden tabsize is sent to language server
10242    update_test_language_settings(cx, |settings| {
10243        settings.languages.0.insert(
10244            "Rust".into(),
10245            LanguageSettingsContent {
10246                tab_size: NonZeroU32::new(8),
10247                ..Default::default()
10248            },
10249        );
10250    });
10251
10252    editor.update_in(cx, |editor, window, cx| {
10253        editor.set_text("something_new\n", window, cx)
10254    });
10255    assert!(cx.read(|cx| editor.is_dirty(cx)));
10256    let save = editor
10257        .update_in(cx, |editor, window, cx| {
10258            editor.save(
10259                SaveOptions {
10260                    format: true,
10261                    autosave: false,
10262                },
10263                project.clone(),
10264                window,
10265                cx,
10266            )
10267        })
10268        .unwrap();
10269    fake_server
10270        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10271            assert_eq!(
10272                params.text_document.uri,
10273                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10274            );
10275            assert_eq!(params.options.tab_size, 8);
10276            Ok(Some(Vec::new()))
10277        })
10278        .next()
10279        .await;
10280    save.await;
10281}
10282
10283#[gpui::test]
10284async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10285    init_test(cx, |settings| {
10286        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10287            Formatter::LanguageServer { name: None },
10288        )))
10289    });
10290
10291    let fs = FakeFs::new(cx.executor());
10292    fs.insert_file(path!("/file.rs"), Default::default()).await;
10293
10294    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10295
10296    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10297    language_registry.add(Arc::new(Language::new(
10298        LanguageConfig {
10299            name: "Rust".into(),
10300            matcher: LanguageMatcher {
10301                path_suffixes: vec!["rs".to_string()],
10302                ..Default::default()
10303            },
10304            ..LanguageConfig::default()
10305        },
10306        Some(tree_sitter_rust::LANGUAGE.into()),
10307    )));
10308    update_test_language_settings(cx, |settings| {
10309        // Enable Prettier formatting for the same buffer, and ensure
10310        // LSP is called instead of Prettier.
10311        settings.defaults.prettier = Some(PrettierSettings {
10312            allowed: true,
10313            ..PrettierSettings::default()
10314        });
10315    });
10316    let mut fake_servers = language_registry.register_fake_lsp(
10317        "Rust",
10318        FakeLspAdapter {
10319            capabilities: lsp::ServerCapabilities {
10320                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10321                ..Default::default()
10322            },
10323            ..Default::default()
10324        },
10325    );
10326
10327    let buffer = project
10328        .update(cx, |project, cx| {
10329            project.open_local_buffer(path!("/file.rs"), cx)
10330        })
10331        .await
10332        .unwrap();
10333
10334    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10335    let (editor, cx) = cx.add_window_view(|window, cx| {
10336        build_editor_with_project(project.clone(), buffer, window, cx)
10337    });
10338    editor.update_in(cx, |editor, window, cx| {
10339        editor.set_text("one\ntwo\nthree\n", window, cx)
10340    });
10341
10342    cx.executor().start_waiting();
10343    let fake_server = fake_servers.next().await.unwrap();
10344
10345    let format = editor
10346        .update_in(cx, |editor, window, cx| {
10347            editor.perform_format(
10348                project.clone(),
10349                FormatTrigger::Manual,
10350                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10351                window,
10352                cx,
10353            )
10354        })
10355        .unwrap();
10356    fake_server
10357        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10358            assert_eq!(
10359                params.text_document.uri,
10360                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10361            );
10362            assert_eq!(params.options.tab_size, 4);
10363            Ok(Some(vec![lsp::TextEdit::new(
10364                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10365                ", ".to_string(),
10366            )]))
10367        })
10368        .next()
10369        .await;
10370    cx.executor().start_waiting();
10371    format.await;
10372    assert_eq!(
10373        editor.update(cx, |editor, cx| editor.text(cx)),
10374        "one, two\nthree\n"
10375    );
10376
10377    editor.update_in(cx, |editor, window, cx| {
10378        editor.set_text("one\ntwo\nthree\n", window, cx)
10379    });
10380    // Ensure we don't lock if formatting hangs.
10381    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10382        move |params, _| async move {
10383            assert_eq!(
10384                params.text_document.uri,
10385                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10386            );
10387            futures::future::pending::<()>().await;
10388            unreachable!()
10389        },
10390    );
10391    let format = editor
10392        .update_in(cx, |editor, window, cx| {
10393            editor.perform_format(
10394                project,
10395                FormatTrigger::Manual,
10396                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10397                window,
10398                cx,
10399            )
10400        })
10401        .unwrap();
10402    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10403    cx.executor().start_waiting();
10404    format.await;
10405    assert_eq!(
10406        editor.update(cx, |editor, cx| editor.text(cx)),
10407        "one\ntwo\nthree\n"
10408    );
10409}
10410
10411#[gpui::test]
10412async fn test_multiple_formatters(cx: &mut TestAppContext) {
10413    init_test(cx, |settings| {
10414        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10415        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10416            Formatter::LanguageServer { name: None },
10417            Formatter::CodeActions(
10418                [
10419                    ("code-action-1".into(), true),
10420                    ("code-action-2".into(), true),
10421                ]
10422                .into_iter()
10423                .collect(),
10424            ),
10425        ])))
10426    });
10427
10428    let fs = FakeFs::new(cx.executor());
10429    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10430        .await;
10431
10432    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10433    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10434    language_registry.add(rust_lang());
10435
10436    let mut fake_servers = language_registry.register_fake_lsp(
10437        "Rust",
10438        FakeLspAdapter {
10439            capabilities: lsp::ServerCapabilities {
10440                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10441                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10442                    commands: vec!["the-command-for-code-action-1".into()],
10443                    ..Default::default()
10444                }),
10445                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10446                ..Default::default()
10447            },
10448            ..Default::default()
10449        },
10450    );
10451
10452    let buffer = project
10453        .update(cx, |project, cx| {
10454            project.open_local_buffer(path!("/file.rs"), cx)
10455        })
10456        .await
10457        .unwrap();
10458
10459    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10460    let (editor, cx) = cx.add_window_view(|window, cx| {
10461        build_editor_with_project(project.clone(), buffer, window, cx)
10462    });
10463
10464    cx.executor().start_waiting();
10465
10466    let fake_server = fake_servers.next().await.unwrap();
10467    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10468        move |_params, _| async move {
10469            Ok(Some(vec![lsp::TextEdit::new(
10470                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10471                "applied-formatting\n".to_string(),
10472            )]))
10473        },
10474    );
10475    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10476        move |params, _| async move {
10477            assert_eq!(
10478                params.context.only,
10479                Some(vec!["code-action-1".into(), "code-action-2".into()])
10480            );
10481            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10482            Ok(Some(vec![
10483                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10484                    kind: Some("code-action-1".into()),
10485                    edit: Some(lsp::WorkspaceEdit::new(
10486                        [(
10487                            uri.clone(),
10488                            vec![lsp::TextEdit::new(
10489                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10490                                "applied-code-action-1-edit\n".to_string(),
10491                            )],
10492                        )]
10493                        .into_iter()
10494                        .collect(),
10495                    )),
10496                    command: Some(lsp::Command {
10497                        command: "the-command-for-code-action-1".into(),
10498                        ..Default::default()
10499                    }),
10500                    ..Default::default()
10501                }),
10502                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10503                    kind: Some("code-action-2".into()),
10504                    edit: Some(lsp::WorkspaceEdit::new(
10505                        [(
10506                            uri.clone(),
10507                            vec![lsp::TextEdit::new(
10508                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10509                                "applied-code-action-2-edit\n".to_string(),
10510                            )],
10511                        )]
10512                        .into_iter()
10513                        .collect(),
10514                    )),
10515                    ..Default::default()
10516                }),
10517            ]))
10518        },
10519    );
10520
10521    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10522        move |params, _| async move { Ok(params) }
10523    });
10524
10525    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10526    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10527        let fake = fake_server.clone();
10528        let lock = command_lock.clone();
10529        move |params, _| {
10530            assert_eq!(params.command, "the-command-for-code-action-1");
10531            let fake = fake.clone();
10532            let lock = lock.clone();
10533            async move {
10534                lock.lock().await;
10535                fake.server
10536                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10537                        label: None,
10538                        edit: lsp::WorkspaceEdit {
10539                            changes: Some(
10540                                [(
10541                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10542                                    vec![lsp::TextEdit {
10543                                        range: lsp::Range::new(
10544                                            lsp::Position::new(0, 0),
10545                                            lsp::Position::new(0, 0),
10546                                        ),
10547                                        new_text: "applied-code-action-1-command\n".into(),
10548                                    }],
10549                                )]
10550                                .into_iter()
10551                                .collect(),
10552                            ),
10553                            ..Default::default()
10554                        },
10555                    })
10556                    .await
10557                    .into_response()
10558                    .unwrap();
10559                Ok(Some(json!(null)))
10560            }
10561        }
10562    });
10563
10564    cx.executor().start_waiting();
10565    editor
10566        .update_in(cx, |editor, window, cx| {
10567            editor.perform_format(
10568                project.clone(),
10569                FormatTrigger::Manual,
10570                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10571                window,
10572                cx,
10573            )
10574        })
10575        .unwrap()
10576        .await;
10577    editor.update(cx, |editor, cx| {
10578        assert_eq!(
10579            editor.text(cx),
10580            r#"
10581                applied-code-action-2-edit
10582                applied-code-action-1-command
10583                applied-code-action-1-edit
10584                applied-formatting
10585                one
10586                two
10587                three
10588            "#
10589            .unindent()
10590        );
10591    });
10592
10593    editor.update_in(cx, |editor, window, cx| {
10594        editor.undo(&Default::default(), window, cx);
10595        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10596    });
10597
10598    // Perform a manual edit while waiting for an LSP command
10599    // that's being run as part of a formatting code action.
10600    let lock_guard = command_lock.lock().await;
10601    let format = editor
10602        .update_in(cx, |editor, window, cx| {
10603            editor.perform_format(
10604                project.clone(),
10605                FormatTrigger::Manual,
10606                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10607                window,
10608                cx,
10609            )
10610        })
10611        .unwrap();
10612    cx.run_until_parked();
10613    editor.update(cx, |editor, cx| {
10614        assert_eq!(
10615            editor.text(cx),
10616            r#"
10617                applied-code-action-1-edit
10618                applied-formatting
10619                one
10620                two
10621                three
10622            "#
10623            .unindent()
10624        );
10625
10626        editor.buffer.update(cx, |buffer, cx| {
10627            let ix = buffer.len(cx);
10628            buffer.edit([(ix..ix, "edited\n")], None, cx);
10629        });
10630    });
10631
10632    // Allow the LSP command to proceed. Because the buffer was edited,
10633    // the second code action will not be run.
10634    drop(lock_guard);
10635    format.await;
10636    editor.update_in(cx, |editor, window, cx| {
10637        assert_eq!(
10638            editor.text(cx),
10639            r#"
10640                applied-code-action-1-command
10641                applied-code-action-1-edit
10642                applied-formatting
10643                one
10644                two
10645                three
10646                edited
10647            "#
10648            .unindent()
10649        );
10650
10651        // The manual edit is undone first, because it is the last thing the user did
10652        // (even though the command completed afterwards).
10653        editor.undo(&Default::default(), window, cx);
10654        assert_eq!(
10655            editor.text(cx),
10656            r#"
10657                applied-code-action-1-command
10658                applied-code-action-1-edit
10659                applied-formatting
10660                one
10661                two
10662                three
10663            "#
10664            .unindent()
10665        );
10666
10667        // All the formatting (including the command, which completed after the manual edit)
10668        // is undone together.
10669        editor.undo(&Default::default(), window, cx);
10670        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10671    });
10672}
10673
10674#[gpui::test]
10675async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10676    init_test(cx, |settings| {
10677        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10678            Formatter::LanguageServer { name: None },
10679        ])))
10680    });
10681
10682    let fs = FakeFs::new(cx.executor());
10683    fs.insert_file(path!("/file.ts"), Default::default()).await;
10684
10685    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10686
10687    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10688    language_registry.add(Arc::new(Language::new(
10689        LanguageConfig {
10690            name: "TypeScript".into(),
10691            matcher: LanguageMatcher {
10692                path_suffixes: vec!["ts".to_string()],
10693                ..Default::default()
10694            },
10695            ..LanguageConfig::default()
10696        },
10697        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10698    )));
10699    update_test_language_settings(cx, |settings| {
10700        settings.defaults.prettier = Some(PrettierSettings {
10701            allowed: true,
10702            ..PrettierSettings::default()
10703        });
10704    });
10705    let mut fake_servers = language_registry.register_fake_lsp(
10706        "TypeScript",
10707        FakeLspAdapter {
10708            capabilities: lsp::ServerCapabilities {
10709                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10710                ..Default::default()
10711            },
10712            ..Default::default()
10713        },
10714    );
10715
10716    let buffer = project
10717        .update(cx, |project, cx| {
10718            project.open_local_buffer(path!("/file.ts"), cx)
10719        })
10720        .await
10721        .unwrap();
10722
10723    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10724    let (editor, cx) = cx.add_window_view(|window, cx| {
10725        build_editor_with_project(project.clone(), buffer, window, cx)
10726    });
10727    editor.update_in(cx, |editor, window, cx| {
10728        editor.set_text(
10729            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10730            window,
10731            cx,
10732        )
10733    });
10734
10735    cx.executor().start_waiting();
10736    let fake_server = fake_servers.next().await.unwrap();
10737
10738    let format = editor
10739        .update_in(cx, |editor, window, cx| {
10740            editor.perform_code_action_kind(
10741                project.clone(),
10742                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10743                window,
10744                cx,
10745            )
10746        })
10747        .unwrap();
10748    fake_server
10749        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10750            assert_eq!(
10751                params.text_document.uri,
10752                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10753            );
10754            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10755                lsp::CodeAction {
10756                    title: "Organize Imports".to_string(),
10757                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10758                    edit: Some(lsp::WorkspaceEdit {
10759                        changes: Some(
10760                            [(
10761                                params.text_document.uri.clone(),
10762                                vec![lsp::TextEdit::new(
10763                                    lsp::Range::new(
10764                                        lsp::Position::new(1, 0),
10765                                        lsp::Position::new(2, 0),
10766                                    ),
10767                                    "".to_string(),
10768                                )],
10769                            )]
10770                            .into_iter()
10771                            .collect(),
10772                        ),
10773                        ..Default::default()
10774                    }),
10775                    ..Default::default()
10776                },
10777            )]))
10778        })
10779        .next()
10780        .await;
10781    cx.executor().start_waiting();
10782    format.await;
10783    assert_eq!(
10784        editor.update(cx, |editor, cx| editor.text(cx)),
10785        "import { a } from 'module';\n\nconst x = a;\n"
10786    );
10787
10788    editor.update_in(cx, |editor, window, cx| {
10789        editor.set_text(
10790            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10791            window,
10792            cx,
10793        )
10794    });
10795    // Ensure we don't lock if code action hangs.
10796    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10797        move |params, _| async move {
10798            assert_eq!(
10799                params.text_document.uri,
10800                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10801            );
10802            futures::future::pending::<()>().await;
10803            unreachable!()
10804        },
10805    );
10806    let format = editor
10807        .update_in(cx, |editor, window, cx| {
10808            editor.perform_code_action_kind(
10809                project,
10810                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10811                window,
10812                cx,
10813            )
10814        })
10815        .unwrap();
10816    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10817    cx.executor().start_waiting();
10818    format.await;
10819    assert_eq!(
10820        editor.update(cx, |editor, cx| editor.text(cx)),
10821        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10822    );
10823}
10824
10825#[gpui::test]
10826async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10827    init_test(cx, |_| {});
10828
10829    let mut cx = EditorLspTestContext::new_rust(
10830        lsp::ServerCapabilities {
10831            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10832            ..Default::default()
10833        },
10834        cx,
10835    )
10836    .await;
10837
10838    cx.set_state(indoc! {"
10839        one.twoˇ
10840    "});
10841
10842    // The format request takes a long time. When it completes, it inserts
10843    // a newline and an indent before the `.`
10844    cx.lsp
10845        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10846            let executor = cx.background_executor().clone();
10847            async move {
10848                executor.timer(Duration::from_millis(100)).await;
10849                Ok(Some(vec![lsp::TextEdit {
10850                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10851                    new_text: "\n    ".into(),
10852                }]))
10853            }
10854        });
10855
10856    // Submit a format request.
10857    let format_1 = cx
10858        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10859        .unwrap();
10860    cx.executor().run_until_parked();
10861
10862    // Submit a second format request.
10863    let format_2 = cx
10864        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10865        .unwrap();
10866    cx.executor().run_until_parked();
10867
10868    // Wait for both format requests to complete
10869    cx.executor().advance_clock(Duration::from_millis(200));
10870    cx.executor().start_waiting();
10871    format_1.await.unwrap();
10872    cx.executor().start_waiting();
10873    format_2.await.unwrap();
10874
10875    // The formatting edits only happens once.
10876    cx.assert_editor_state(indoc! {"
10877        one
10878            .twoˇ
10879    "});
10880}
10881
10882#[gpui::test]
10883async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10884    init_test(cx, |settings| {
10885        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10886    });
10887
10888    let mut cx = EditorLspTestContext::new_rust(
10889        lsp::ServerCapabilities {
10890            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10891            ..Default::default()
10892        },
10893        cx,
10894    )
10895    .await;
10896
10897    // Set up a buffer white some trailing whitespace and no trailing newline.
10898    cx.set_state(
10899        &[
10900            "one ",   //
10901            "twoˇ",   //
10902            "three ", //
10903            "four",   //
10904        ]
10905        .join("\n"),
10906    );
10907
10908    // Submit a format request.
10909    let format = cx
10910        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10911        .unwrap();
10912
10913    // Record which buffer changes have been sent to the language server
10914    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10915    cx.lsp
10916        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10917            let buffer_changes = buffer_changes.clone();
10918            move |params, _| {
10919                buffer_changes.lock().extend(
10920                    params
10921                        .content_changes
10922                        .into_iter()
10923                        .map(|e| (e.range.unwrap(), e.text)),
10924                );
10925            }
10926        });
10927
10928    // Handle formatting requests to the language server.
10929    cx.lsp
10930        .set_request_handler::<lsp::request::Formatting, _, _>({
10931            let buffer_changes = buffer_changes.clone();
10932            move |_, _| {
10933                // When formatting is requested, trailing whitespace has already been stripped,
10934                // and the trailing newline has already been added.
10935                assert_eq!(
10936                    &buffer_changes.lock()[1..],
10937                    &[
10938                        (
10939                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10940                            "".into()
10941                        ),
10942                        (
10943                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10944                            "".into()
10945                        ),
10946                        (
10947                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10948                            "\n".into()
10949                        ),
10950                    ]
10951                );
10952
10953                // Insert blank lines between each line of the buffer.
10954                async move {
10955                    Ok(Some(vec![
10956                        lsp::TextEdit {
10957                            range: lsp::Range::new(
10958                                lsp::Position::new(1, 0),
10959                                lsp::Position::new(1, 0),
10960                            ),
10961                            new_text: "\n".into(),
10962                        },
10963                        lsp::TextEdit {
10964                            range: lsp::Range::new(
10965                                lsp::Position::new(2, 0),
10966                                lsp::Position::new(2, 0),
10967                            ),
10968                            new_text: "\n".into(),
10969                        },
10970                    ]))
10971                }
10972            }
10973        });
10974
10975    // After formatting the buffer, the trailing whitespace is stripped,
10976    // a newline is appended, and the edits provided by the language server
10977    // have been applied.
10978    format.await.unwrap();
10979    cx.assert_editor_state(
10980        &[
10981            "one",   //
10982            "",      //
10983            "twoˇ",  //
10984            "",      //
10985            "three", //
10986            "four",  //
10987            "",      //
10988        ]
10989        .join("\n"),
10990    );
10991
10992    // Undoing the formatting undoes the trailing whitespace removal, the
10993    // trailing newline, and the LSP edits.
10994    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10995    cx.assert_editor_state(
10996        &[
10997            "one ",   //
10998            "twoˇ",   //
10999            "three ", //
11000            "four",   //
11001        ]
11002        .join("\n"),
11003    );
11004}
11005
11006#[gpui::test]
11007async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11008    cx: &mut TestAppContext,
11009) {
11010    init_test(cx, |_| {});
11011
11012    cx.update(|cx| {
11013        cx.update_global::<SettingsStore, _>(|settings, cx| {
11014            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11015                settings.auto_signature_help = Some(true);
11016            });
11017        });
11018    });
11019
11020    let mut cx = EditorLspTestContext::new_rust(
11021        lsp::ServerCapabilities {
11022            signature_help_provider: Some(lsp::SignatureHelpOptions {
11023                ..Default::default()
11024            }),
11025            ..Default::default()
11026        },
11027        cx,
11028    )
11029    .await;
11030
11031    let language = Language::new(
11032        LanguageConfig {
11033            name: "Rust".into(),
11034            brackets: BracketPairConfig {
11035                pairs: vec![
11036                    BracketPair {
11037                        start: "{".to_string(),
11038                        end: "}".to_string(),
11039                        close: true,
11040                        surround: true,
11041                        newline: true,
11042                    },
11043                    BracketPair {
11044                        start: "(".to_string(),
11045                        end: ")".to_string(),
11046                        close: true,
11047                        surround: true,
11048                        newline: true,
11049                    },
11050                    BracketPair {
11051                        start: "/*".to_string(),
11052                        end: " */".to_string(),
11053                        close: true,
11054                        surround: true,
11055                        newline: true,
11056                    },
11057                    BracketPair {
11058                        start: "[".to_string(),
11059                        end: "]".to_string(),
11060                        close: false,
11061                        surround: false,
11062                        newline: true,
11063                    },
11064                    BracketPair {
11065                        start: "\"".to_string(),
11066                        end: "\"".to_string(),
11067                        close: true,
11068                        surround: true,
11069                        newline: false,
11070                    },
11071                    BracketPair {
11072                        start: "<".to_string(),
11073                        end: ">".to_string(),
11074                        close: false,
11075                        surround: true,
11076                        newline: true,
11077                    },
11078                ],
11079                ..Default::default()
11080            },
11081            autoclose_before: "})]".to_string(),
11082            ..Default::default()
11083        },
11084        Some(tree_sitter_rust::LANGUAGE.into()),
11085    );
11086    let language = Arc::new(language);
11087
11088    cx.language_registry().add(language.clone());
11089    cx.update_buffer(|buffer, cx| {
11090        buffer.set_language(Some(language), cx);
11091    });
11092
11093    cx.set_state(
11094        &r#"
11095            fn main() {
11096                sampleˇ
11097            }
11098        "#
11099        .unindent(),
11100    );
11101
11102    cx.update_editor(|editor, window, cx| {
11103        editor.handle_input("(", window, cx);
11104    });
11105    cx.assert_editor_state(
11106        &"
11107            fn main() {
11108                sample(ˇ)
11109            }
11110        "
11111        .unindent(),
11112    );
11113
11114    let mocked_response = lsp::SignatureHelp {
11115        signatures: vec![lsp::SignatureInformation {
11116            label: "fn sample(param1: u8, param2: u8)".to_string(),
11117            documentation: None,
11118            parameters: Some(vec![
11119                lsp::ParameterInformation {
11120                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11121                    documentation: None,
11122                },
11123                lsp::ParameterInformation {
11124                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11125                    documentation: None,
11126                },
11127            ]),
11128            active_parameter: None,
11129        }],
11130        active_signature: Some(0),
11131        active_parameter: Some(0),
11132    };
11133    handle_signature_help_request(&mut cx, mocked_response).await;
11134
11135    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11136        .await;
11137
11138    cx.editor(|editor, _, _| {
11139        let signature_help_state = editor.signature_help_state.popover().cloned();
11140        let signature = signature_help_state.unwrap();
11141        assert_eq!(
11142            signature.signatures[signature.current_signature].label,
11143            "fn sample(param1: u8, param2: u8)"
11144        );
11145    });
11146}
11147
11148#[gpui::test]
11149async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11150    init_test(cx, |_| {});
11151
11152    cx.update(|cx| {
11153        cx.update_global::<SettingsStore, _>(|settings, cx| {
11154            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11155                settings.auto_signature_help = Some(false);
11156                settings.show_signature_help_after_edits = Some(false);
11157            });
11158        });
11159    });
11160
11161    let mut cx = EditorLspTestContext::new_rust(
11162        lsp::ServerCapabilities {
11163            signature_help_provider: Some(lsp::SignatureHelpOptions {
11164                ..Default::default()
11165            }),
11166            ..Default::default()
11167        },
11168        cx,
11169    )
11170    .await;
11171
11172    let language = Language::new(
11173        LanguageConfig {
11174            name: "Rust".into(),
11175            brackets: BracketPairConfig {
11176                pairs: vec![
11177                    BracketPair {
11178                        start: "{".to_string(),
11179                        end: "}".to_string(),
11180                        close: true,
11181                        surround: true,
11182                        newline: true,
11183                    },
11184                    BracketPair {
11185                        start: "(".to_string(),
11186                        end: ")".to_string(),
11187                        close: true,
11188                        surround: true,
11189                        newline: true,
11190                    },
11191                    BracketPair {
11192                        start: "/*".to_string(),
11193                        end: " */".to_string(),
11194                        close: true,
11195                        surround: true,
11196                        newline: true,
11197                    },
11198                    BracketPair {
11199                        start: "[".to_string(),
11200                        end: "]".to_string(),
11201                        close: false,
11202                        surround: false,
11203                        newline: true,
11204                    },
11205                    BracketPair {
11206                        start: "\"".to_string(),
11207                        end: "\"".to_string(),
11208                        close: true,
11209                        surround: true,
11210                        newline: false,
11211                    },
11212                    BracketPair {
11213                        start: "<".to_string(),
11214                        end: ">".to_string(),
11215                        close: false,
11216                        surround: true,
11217                        newline: true,
11218                    },
11219                ],
11220                ..Default::default()
11221            },
11222            autoclose_before: "})]".to_string(),
11223            ..Default::default()
11224        },
11225        Some(tree_sitter_rust::LANGUAGE.into()),
11226    );
11227    let language = Arc::new(language);
11228
11229    cx.language_registry().add(language.clone());
11230    cx.update_buffer(|buffer, cx| {
11231        buffer.set_language(Some(language), cx);
11232    });
11233
11234    // Ensure that signature_help is not called when no signature help is enabled.
11235    cx.set_state(
11236        &r#"
11237            fn main() {
11238                sampleˇ
11239            }
11240        "#
11241        .unindent(),
11242    );
11243    cx.update_editor(|editor, window, cx| {
11244        editor.handle_input("(", window, cx);
11245    });
11246    cx.assert_editor_state(
11247        &"
11248            fn main() {
11249                sample(ˇ)
11250            }
11251        "
11252        .unindent(),
11253    );
11254    cx.editor(|editor, _, _| {
11255        assert!(editor.signature_help_state.task().is_none());
11256    });
11257
11258    let mocked_response = lsp::SignatureHelp {
11259        signatures: vec![lsp::SignatureInformation {
11260            label: "fn sample(param1: u8, param2: u8)".to_string(),
11261            documentation: None,
11262            parameters: Some(vec![
11263                lsp::ParameterInformation {
11264                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11265                    documentation: None,
11266                },
11267                lsp::ParameterInformation {
11268                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11269                    documentation: None,
11270                },
11271            ]),
11272            active_parameter: None,
11273        }],
11274        active_signature: Some(0),
11275        active_parameter: Some(0),
11276    };
11277
11278    // Ensure that signature_help is called when enabled afte edits
11279    cx.update(|_, cx| {
11280        cx.update_global::<SettingsStore, _>(|settings, cx| {
11281            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11282                settings.auto_signature_help = Some(false);
11283                settings.show_signature_help_after_edits = Some(true);
11284            });
11285        });
11286    });
11287    cx.set_state(
11288        &r#"
11289            fn main() {
11290                sampleˇ
11291            }
11292        "#
11293        .unindent(),
11294    );
11295    cx.update_editor(|editor, window, cx| {
11296        editor.handle_input("(", window, cx);
11297    });
11298    cx.assert_editor_state(
11299        &"
11300            fn main() {
11301                sample(ˇ)
11302            }
11303        "
11304        .unindent(),
11305    );
11306    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11307    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11308        .await;
11309    cx.update_editor(|editor, _, _| {
11310        let signature_help_state = editor.signature_help_state.popover().cloned();
11311        assert!(signature_help_state.is_some());
11312        let signature = signature_help_state.unwrap();
11313        assert_eq!(
11314            signature.signatures[signature.current_signature].label,
11315            "fn sample(param1: u8, param2: u8)"
11316        );
11317        editor.signature_help_state = SignatureHelpState::default();
11318    });
11319
11320    // Ensure that signature_help is called when auto signature help override is enabled
11321    cx.update(|_, cx| {
11322        cx.update_global::<SettingsStore, _>(|settings, cx| {
11323            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11324                settings.auto_signature_help = Some(true);
11325                settings.show_signature_help_after_edits = Some(false);
11326            });
11327        });
11328    });
11329    cx.set_state(
11330        &r#"
11331            fn main() {
11332                sampleˇ
11333            }
11334        "#
11335        .unindent(),
11336    );
11337    cx.update_editor(|editor, window, cx| {
11338        editor.handle_input("(", window, cx);
11339    });
11340    cx.assert_editor_state(
11341        &"
11342            fn main() {
11343                sample(ˇ)
11344            }
11345        "
11346        .unindent(),
11347    );
11348    handle_signature_help_request(&mut cx, mocked_response).await;
11349    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11350        .await;
11351    cx.editor(|editor, _, _| {
11352        let signature_help_state = editor.signature_help_state.popover().cloned();
11353        assert!(signature_help_state.is_some());
11354        let signature = signature_help_state.unwrap();
11355        assert_eq!(
11356            signature.signatures[signature.current_signature].label,
11357            "fn sample(param1: u8, param2: u8)"
11358        );
11359    });
11360}
11361
11362#[gpui::test]
11363async fn test_signature_help(cx: &mut TestAppContext) {
11364    init_test(cx, |_| {});
11365    cx.update(|cx| {
11366        cx.update_global::<SettingsStore, _>(|settings, cx| {
11367            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11368                settings.auto_signature_help = Some(true);
11369            });
11370        });
11371    });
11372
11373    let mut cx = EditorLspTestContext::new_rust(
11374        lsp::ServerCapabilities {
11375            signature_help_provider: Some(lsp::SignatureHelpOptions {
11376                ..Default::default()
11377            }),
11378            ..Default::default()
11379        },
11380        cx,
11381    )
11382    .await;
11383
11384    // A test that directly calls `show_signature_help`
11385    cx.update_editor(|editor, window, cx| {
11386        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11387    });
11388
11389    let mocked_response = lsp::SignatureHelp {
11390        signatures: vec![lsp::SignatureInformation {
11391            label: "fn sample(param1: u8, param2: u8)".to_string(),
11392            documentation: None,
11393            parameters: Some(vec![
11394                lsp::ParameterInformation {
11395                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11396                    documentation: None,
11397                },
11398                lsp::ParameterInformation {
11399                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11400                    documentation: None,
11401                },
11402            ]),
11403            active_parameter: None,
11404        }],
11405        active_signature: Some(0),
11406        active_parameter: Some(0),
11407    };
11408    handle_signature_help_request(&mut cx, mocked_response).await;
11409
11410    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11411        .await;
11412
11413    cx.editor(|editor, _, _| {
11414        let signature_help_state = editor.signature_help_state.popover().cloned();
11415        assert!(signature_help_state.is_some());
11416        let signature = signature_help_state.unwrap();
11417        assert_eq!(
11418            signature.signatures[signature.current_signature].label,
11419            "fn sample(param1: u8, param2: u8)"
11420        );
11421    });
11422
11423    // When exiting outside from inside the brackets, `signature_help` is closed.
11424    cx.set_state(indoc! {"
11425        fn main() {
11426            sample(ˇ);
11427        }
11428
11429        fn sample(param1: u8, param2: u8) {}
11430    "});
11431
11432    cx.update_editor(|editor, window, cx| {
11433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11434            s.select_ranges([0..0])
11435        });
11436    });
11437
11438    let mocked_response = lsp::SignatureHelp {
11439        signatures: Vec::new(),
11440        active_signature: None,
11441        active_parameter: None,
11442    };
11443    handle_signature_help_request(&mut cx, mocked_response).await;
11444
11445    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11446        .await;
11447
11448    cx.editor(|editor, _, _| {
11449        assert!(!editor.signature_help_state.is_shown());
11450    });
11451
11452    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11453    cx.set_state(indoc! {"
11454        fn main() {
11455            sample(ˇ);
11456        }
11457
11458        fn sample(param1: u8, param2: u8) {}
11459    "});
11460
11461    let mocked_response = lsp::SignatureHelp {
11462        signatures: vec![lsp::SignatureInformation {
11463            label: "fn sample(param1: u8, param2: u8)".to_string(),
11464            documentation: None,
11465            parameters: Some(vec![
11466                lsp::ParameterInformation {
11467                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11468                    documentation: None,
11469                },
11470                lsp::ParameterInformation {
11471                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11472                    documentation: None,
11473                },
11474            ]),
11475            active_parameter: None,
11476        }],
11477        active_signature: Some(0),
11478        active_parameter: Some(0),
11479    };
11480    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11481    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11482        .await;
11483    cx.editor(|editor, _, _| {
11484        assert!(editor.signature_help_state.is_shown());
11485    });
11486
11487    // Restore the popover with more parameter input
11488    cx.set_state(indoc! {"
11489        fn main() {
11490            sample(param1, param2ˇ);
11491        }
11492
11493        fn sample(param1: u8, param2: u8) {}
11494    "});
11495
11496    let mocked_response = lsp::SignatureHelp {
11497        signatures: vec![lsp::SignatureInformation {
11498            label: "fn sample(param1: u8, param2: u8)".to_string(),
11499            documentation: None,
11500            parameters: Some(vec![
11501                lsp::ParameterInformation {
11502                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11503                    documentation: None,
11504                },
11505                lsp::ParameterInformation {
11506                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11507                    documentation: None,
11508                },
11509            ]),
11510            active_parameter: None,
11511        }],
11512        active_signature: Some(0),
11513        active_parameter: Some(1),
11514    };
11515    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11516    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11517        .await;
11518
11519    // When selecting a range, the popover is gone.
11520    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11521    cx.update_editor(|editor, window, cx| {
11522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11523            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11524        })
11525    });
11526    cx.assert_editor_state(indoc! {"
11527        fn main() {
11528            sample(param1, «ˇparam2»);
11529        }
11530
11531        fn sample(param1: u8, param2: u8) {}
11532    "});
11533    cx.editor(|editor, _, _| {
11534        assert!(!editor.signature_help_state.is_shown());
11535    });
11536
11537    // When unselecting again, the popover is back if within the brackets.
11538    cx.update_editor(|editor, window, cx| {
11539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11540            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11541        })
11542    });
11543    cx.assert_editor_state(indoc! {"
11544        fn main() {
11545            sample(param1, ˇparam2);
11546        }
11547
11548        fn sample(param1: u8, param2: u8) {}
11549    "});
11550    handle_signature_help_request(&mut cx, mocked_response).await;
11551    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11552        .await;
11553    cx.editor(|editor, _, _| {
11554        assert!(editor.signature_help_state.is_shown());
11555    });
11556
11557    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11558    cx.update_editor(|editor, window, cx| {
11559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11560            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11561            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11562        })
11563    });
11564    cx.assert_editor_state(indoc! {"
11565        fn main() {
11566            sample(param1, ˇparam2);
11567        }
11568
11569        fn sample(param1: u8, param2: u8) {}
11570    "});
11571
11572    let mocked_response = lsp::SignatureHelp {
11573        signatures: vec![lsp::SignatureInformation {
11574            label: "fn sample(param1: u8, param2: u8)".to_string(),
11575            documentation: None,
11576            parameters: Some(vec![
11577                lsp::ParameterInformation {
11578                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11579                    documentation: None,
11580                },
11581                lsp::ParameterInformation {
11582                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11583                    documentation: None,
11584                },
11585            ]),
11586            active_parameter: None,
11587        }],
11588        active_signature: Some(0),
11589        active_parameter: Some(1),
11590    };
11591    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11592    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11593        .await;
11594    cx.update_editor(|editor, _, cx| {
11595        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11596    });
11597    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11598        .await;
11599    cx.update_editor(|editor, window, cx| {
11600        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11601            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11602        })
11603    });
11604    cx.assert_editor_state(indoc! {"
11605        fn main() {
11606            sample(param1, «ˇparam2»);
11607        }
11608
11609        fn sample(param1: u8, param2: u8) {}
11610    "});
11611    cx.update_editor(|editor, window, cx| {
11612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11613            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11614        })
11615    });
11616    cx.assert_editor_state(indoc! {"
11617        fn main() {
11618            sample(param1, ˇparam2);
11619        }
11620
11621        fn sample(param1: u8, param2: u8) {}
11622    "});
11623    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11624        .await;
11625}
11626
11627#[gpui::test]
11628async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11629    init_test(cx, |_| {});
11630
11631    let mut cx = EditorLspTestContext::new_rust(
11632        lsp::ServerCapabilities {
11633            signature_help_provider: Some(lsp::SignatureHelpOptions {
11634                ..Default::default()
11635            }),
11636            ..Default::default()
11637        },
11638        cx,
11639    )
11640    .await;
11641
11642    cx.set_state(indoc! {"
11643        fn main() {
11644            overloadedˇ
11645        }
11646    "});
11647
11648    cx.update_editor(|editor, window, cx| {
11649        editor.handle_input("(", window, cx);
11650        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11651    });
11652
11653    // Mock response with 3 signatures
11654    let mocked_response = lsp::SignatureHelp {
11655        signatures: vec![
11656            lsp::SignatureInformation {
11657                label: "fn overloaded(x: i32)".to_string(),
11658                documentation: None,
11659                parameters: Some(vec![lsp::ParameterInformation {
11660                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11661                    documentation: None,
11662                }]),
11663                active_parameter: None,
11664            },
11665            lsp::SignatureInformation {
11666                label: "fn overloaded(x: i32, y: i32)".to_string(),
11667                documentation: None,
11668                parameters: Some(vec![
11669                    lsp::ParameterInformation {
11670                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11671                        documentation: None,
11672                    },
11673                    lsp::ParameterInformation {
11674                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11675                        documentation: None,
11676                    },
11677                ]),
11678                active_parameter: None,
11679            },
11680            lsp::SignatureInformation {
11681                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11682                documentation: None,
11683                parameters: Some(vec![
11684                    lsp::ParameterInformation {
11685                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11686                        documentation: None,
11687                    },
11688                    lsp::ParameterInformation {
11689                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11690                        documentation: None,
11691                    },
11692                    lsp::ParameterInformation {
11693                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11694                        documentation: None,
11695                    },
11696                ]),
11697                active_parameter: None,
11698            },
11699        ],
11700        active_signature: Some(1),
11701        active_parameter: Some(0),
11702    };
11703    handle_signature_help_request(&mut cx, mocked_response).await;
11704
11705    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11706        .await;
11707
11708    // Verify we have multiple signatures and the right one is selected
11709    cx.editor(|editor, _, _| {
11710        let popover = editor.signature_help_state.popover().cloned().unwrap();
11711        assert_eq!(popover.signatures.len(), 3);
11712        // active_signature was 1, so that should be the current
11713        assert_eq!(popover.current_signature, 1);
11714        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11715        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11716        assert_eq!(
11717            popover.signatures[2].label,
11718            "fn overloaded(x: i32, y: i32, z: i32)"
11719        );
11720    });
11721
11722    // Test navigation functionality
11723    cx.update_editor(|editor, window, cx| {
11724        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11725    });
11726
11727    cx.editor(|editor, _, _| {
11728        let popover = editor.signature_help_state.popover().cloned().unwrap();
11729        assert_eq!(popover.current_signature, 2);
11730    });
11731
11732    // Test wrap around
11733    cx.update_editor(|editor, window, cx| {
11734        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11735    });
11736
11737    cx.editor(|editor, _, _| {
11738        let popover = editor.signature_help_state.popover().cloned().unwrap();
11739        assert_eq!(popover.current_signature, 0);
11740    });
11741
11742    // Test previous navigation
11743    cx.update_editor(|editor, window, cx| {
11744        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11745    });
11746
11747    cx.editor(|editor, _, _| {
11748        let popover = editor.signature_help_state.popover().cloned().unwrap();
11749        assert_eq!(popover.current_signature, 2);
11750    });
11751}
11752
11753#[gpui::test]
11754async fn test_completion_mode(cx: &mut TestAppContext) {
11755    init_test(cx, |_| {});
11756    let mut cx = EditorLspTestContext::new_rust(
11757        lsp::ServerCapabilities {
11758            completion_provider: Some(lsp::CompletionOptions {
11759                resolve_provider: Some(true),
11760                ..Default::default()
11761            }),
11762            ..Default::default()
11763        },
11764        cx,
11765    )
11766    .await;
11767
11768    struct Run {
11769        run_description: &'static str,
11770        initial_state: String,
11771        buffer_marked_text: String,
11772        completion_label: &'static str,
11773        completion_text: &'static str,
11774        expected_with_insert_mode: String,
11775        expected_with_replace_mode: String,
11776        expected_with_replace_subsequence_mode: String,
11777        expected_with_replace_suffix_mode: String,
11778    }
11779
11780    let runs = [
11781        Run {
11782            run_description: "Start of word matches completion text",
11783            initial_state: "before ediˇ after".into(),
11784            buffer_marked_text: "before <edi|> after".into(),
11785            completion_label: "editor",
11786            completion_text: "editor",
11787            expected_with_insert_mode: "before editorˇ after".into(),
11788            expected_with_replace_mode: "before editorˇ after".into(),
11789            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11790            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11791        },
11792        Run {
11793            run_description: "Accept same text at the middle of the word",
11794            initial_state: "before ediˇtor after".into(),
11795            buffer_marked_text: "before <edi|tor> after".into(),
11796            completion_label: "editor",
11797            completion_text: "editor",
11798            expected_with_insert_mode: "before editorˇtor after".into(),
11799            expected_with_replace_mode: "before editorˇ after".into(),
11800            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11801            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11802        },
11803        Run {
11804            run_description: "End of word matches completion text -- cursor at end",
11805            initial_state: "before torˇ after".into(),
11806            buffer_marked_text: "before <tor|> after".into(),
11807            completion_label: "editor",
11808            completion_text: "editor",
11809            expected_with_insert_mode: "before editorˇ after".into(),
11810            expected_with_replace_mode: "before editorˇ after".into(),
11811            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11812            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11813        },
11814        Run {
11815            run_description: "End of word matches completion text -- cursor at start",
11816            initial_state: "before ˇtor after".into(),
11817            buffer_marked_text: "before <|tor> after".into(),
11818            completion_label: "editor",
11819            completion_text: "editor",
11820            expected_with_insert_mode: "before editorˇtor after".into(),
11821            expected_with_replace_mode: "before editorˇ after".into(),
11822            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11823            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11824        },
11825        Run {
11826            run_description: "Prepend text containing whitespace",
11827            initial_state: "pˇfield: bool".into(),
11828            buffer_marked_text: "<p|field>: bool".into(),
11829            completion_label: "pub ",
11830            completion_text: "pub ",
11831            expected_with_insert_mode: "pub ˇfield: bool".into(),
11832            expected_with_replace_mode: "pub ˇ: bool".into(),
11833            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11834            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11835        },
11836        Run {
11837            run_description: "Add element to start of list",
11838            initial_state: "[element_ˇelement_2]".into(),
11839            buffer_marked_text: "[<element_|element_2>]".into(),
11840            completion_label: "element_1",
11841            completion_text: "element_1",
11842            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11843            expected_with_replace_mode: "[element_1ˇ]".into(),
11844            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11845            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11846        },
11847        Run {
11848            run_description: "Add element to start of list -- first and second elements are equal",
11849            initial_state: "[elˇelement]".into(),
11850            buffer_marked_text: "[<el|element>]".into(),
11851            completion_label: "element",
11852            completion_text: "element",
11853            expected_with_insert_mode: "[elementˇelement]".into(),
11854            expected_with_replace_mode: "[elementˇ]".into(),
11855            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11856            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11857        },
11858        Run {
11859            run_description: "Ends with matching suffix",
11860            initial_state: "SubˇError".into(),
11861            buffer_marked_text: "<Sub|Error>".into(),
11862            completion_label: "SubscriptionError",
11863            completion_text: "SubscriptionError",
11864            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11865            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11866            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11867            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11868        },
11869        Run {
11870            run_description: "Suffix is a subsequence -- contiguous",
11871            initial_state: "SubˇErr".into(),
11872            buffer_marked_text: "<Sub|Err>".into(),
11873            completion_label: "SubscriptionError",
11874            completion_text: "SubscriptionError",
11875            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11876            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11877            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11878            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11879        },
11880        Run {
11881            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11882            initial_state: "Suˇscrirr".into(),
11883            buffer_marked_text: "<Su|scrirr>".into(),
11884            completion_label: "SubscriptionError",
11885            completion_text: "SubscriptionError",
11886            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11887            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11888            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11889            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11890        },
11891        Run {
11892            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11893            initial_state: "foo(indˇix)".into(),
11894            buffer_marked_text: "foo(<ind|ix>)".into(),
11895            completion_label: "node_index",
11896            completion_text: "node_index",
11897            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11898            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11899            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11900            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11901        },
11902        Run {
11903            run_description: "Replace range ends before cursor - should extend to cursor",
11904            initial_state: "before editˇo after".into(),
11905            buffer_marked_text: "before <{ed}>it|o after".into(),
11906            completion_label: "editor",
11907            completion_text: "editor",
11908            expected_with_insert_mode: "before editorˇo after".into(),
11909            expected_with_replace_mode: "before editorˇo after".into(),
11910            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11911            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11912        },
11913        Run {
11914            run_description: "Uses label for suffix matching",
11915            initial_state: "before ediˇtor after".into(),
11916            buffer_marked_text: "before <edi|tor> after".into(),
11917            completion_label: "editor",
11918            completion_text: "editor()",
11919            expected_with_insert_mode: "before editor()ˇtor after".into(),
11920            expected_with_replace_mode: "before editor()ˇ after".into(),
11921            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11922            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11923        },
11924        Run {
11925            run_description: "Case insensitive subsequence and suffix matching",
11926            initial_state: "before EDiˇtoR after".into(),
11927            buffer_marked_text: "before <EDi|toR> after".into(),
11928            completion_label: "editor",
11929            completion_text: "editor",
11930            expected_with_insert_mode: "before editorˇtoR after".into(),
11931            expected_with_replace_mode: "before editorˇ after".into(),
11932            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11933            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11934        },
11935    ];
11936
11937    for run in runs {
11938        let run_variations = [
11939            (LspInsertMode::Insert, run.expected_with_insert_mode),
11940            (LspInsertMode::Replace, run.expected_with_replace_mode),
11941            (
11942                LspInsertMode::ReplaceSubsequence,
11943                run.expected_with_replace_subsequence_mode,
11944            ),
11945            (
11946                LspInsertMode::ReplaceSuffix,
11947                run.expected_with_replace_suffix_mode,
11948            ),
11949        ];
11950
11951        for (lsp_insert_mode, expected_text) in run_variations {
11952            eprintln!(
11953                "run = {:?}, mode = {lsp_insert_mode:.?}",
11954                run.run_description,
11955            );
11956
11957            update_test_language_settings(&mut cx, |settings| {
11958                settings.defaults.completions = Some(CompletionSettings {
11959                    lsp_insert_mode,
11960                    words: WordsCompletionMode::Disabled,
11961                    lsp: true,
11962                    lsp_fetch_timeout_ms: 0,
11963                });
11964            });
11965
11966            cx.set_state(&run.initial_state);
11967            cx.update_editor(|editor, window, cx| {
11968                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11969            });
11970
11971            let counter = Arc::new(AtomicUsize::new(0));
11972            handle_completion_request_with_insert_and_replace(
11973                &mut cx,
11974                &run.buffer_marked_text,
11975                vec![(run.completion_label, run.completion_text)],
11976                counter.clone(),
11977            )
11978            .await;
11979            cx.condition(|editor, _| editor.context_menu_visible())
11980                .await;
11981            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11982
11983            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11984                editor
11985                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11986                    .unwrap()
11987            });
11988            cx.assert_editor_state(&expected_text);
11989            handle_resolve_completion_request(&mut cx, None).await;
11990            apply_additional_edits.await.unwrap();
11991        }
11992    }
11993}
11994
11995#[gpui::test]
11996async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11997    init_test(cx, |_| {});
11998    let mut cx = EditorLspTestContext::new_rust(
11999        lsp::ServerCapabilities {
12000            completion_provider: Some(lsp::CompletionOptions {
12001                resolve_provider: Some(true),
12002                ..Default::default()
12003            }),
12004            ..Default::default()
12005        },
12006        cx,
12007    )
12008    .await;
12009
12010    let initial_state = "SubˇError";
12011    let buffer_marked_text = "<Sub|Error>";
12012    let completion_text = "SubscriptionError";
12013    let expected_with_insert_mode = "SubscriptionErrorˇError";
12014    let expected_with_replace_mode = "SubscriptionErrorˇ";
12015
12016    update_test_language_settings(&mut cx, |settings| {
12017        settings.defaults.completions = Some(CompletionSettings {
12018            words: WordsCompletionMode::Disabled,
12019            // set the opposite here to ensure that the action is overriding the default behavior
12020            lsp_insert_mode: LspInsertMode::Insert,
12021            lsp: true,
12022            lsp_fetch_timeout_ms: 0,
12023        });
12024    });
12025
12026    cx.set_state(initial_state);
12027    cx.update_editor(|editor, window, cx| {
12028        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12029    });
12030
12031    let counter = Arc::new(AtomicUsize::new(0));
12032    handle_completion_request_with_insert_and_replace(
12033        &mut cx,
12034        &buffer_marked_text,
12035        vec![(completion_text, completion_text)],
12036        counter.clone(),
12037    )
12038    .await;
12039    cx.condition(|editor, _| editor.context_menu_visible())
12040        .await;
12041    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12042
12043    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12044        editor
12045            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12046            .unwrap()
12047    });
12048    cx.assert_editor_state(&expected_with_replace_mode);
12049    handle_resolve_completion_request(&mut cx, None).await;
12050    apply_additional_edits.await.unwrap();
12051
12052    update_test_language_settings(&mut cx, |settings| {
12053        settings.defaults.completions = Some(CompletionSettings {
12054            words: WordsCompletionMode::Disabled,
12055            // set the opposite here to ensure that the action is overriding the default behavior
12056            lsp_insert_mode: LspInsertMode::Replace,
12057            lsp: true,
12058            lsp_fetch_timeout_ms: 0,
12059        });
12060    });
12061
12062    cx.set_state(initial_state);
12063    cx.update_editor(|editor, window, cx| {
12064        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12065    });
12066    handle_completion_request_with_insert_and_replace(
12067        &mut cx,
12068        &buffer_marked_text,
12069        vec![(completion_text, completion_text)],
12070        counter.clone(),
12071    )
12072    .await;
12073    cx.condition(|editor, _| editor.context_menu_visible())
12074        .await;
12075    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12076
12077    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12078        editor
12079            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12080            .unwrap()
12081    });
12082    cx.assert_editor_state(&expected_with_insert_mode);
12083    handle_resolve_completion_request(&mut cx, None).await;
12084    apply_additional_edits.await.unwrap();
12085}
12086
12087#[gpui::test]
12088async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12089    init_test(cx, |_| {});
12090    let mut cx = EditorLspTestContext::new_rust(
12091        lsp::ServerCapabilities {
12092            completion_provider: Some(lsp::CompletionOptions {
12093                resolve_provider: Some(true),
12094                ..Default::default()
12095            }),
12096            ..Default::default()
12097        },
12098        cx,
12099    )
12100    .await;
12101
12102    // scenario: surrounding text matches completion text
12103    let completion_text = "to_offset";
12104    let initial_state = indoc! {"
12105        1. buf.to_offˇsuffix
12106        2. buf.to_offˇsuf
12107        3. buf.to_offˇfix
12108        4. buf.to_offˇ
12109        5. into_offˇensive
12110        6. ˇsuffix
12111        7. let ˇ //
12112        8. aaˇzz
12113        9. buf.to_off«zzzzzˇ»suffix
12114        10. buf.«ˇzzzzz»suffix
12115        11. to_off«ˇzzzzz»
12116
12117        buf.to_offˇsuffix  // newest cursor
12118    "};
12119    let completion_marked_buffer = indoc! {"
12120        1. buf.to_offsuffix
12121        2. buf.to_offsuf
12122        3. buf.to_offfix
12123        4. buf.to_off
12124        5. into_offensive
12125        6. suffix
12126        7. let  //
12127        8. aazz
12128        9. buf.to_offzzzzzsuffix
12129        10. buf.zzzzzsuffix
12130        11. to_offzzzzz
12131
12132        buf.<to_off|suffix>  // newest cursor
12133    "};
12134    let expected = indoc! {"
12135        1. buf.to_offsetˇ
12136        2. buf.to_offsetˇsuf
12137        3. buf.to_offsetˇfix
12138        4. buf.to_offsetˇ
12139        5. into_offsetˇensive
12140        6. to_offsetˇsuffix
12141        7. let to_offsetˇ //
12142        8. aato_offsetˇzz
12143        9. buf.to_offsetˇ
12144        10. buf.to_offsetˇsuffix
12145        11. to_offsetˇ
12146
12147        buf.to_offsetˇ  // newest cursor
12148    "};
12149    cx.set_state(initial_state);
12150    cx.update_editor(|editor, window, cx| {
12151        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12152    });
12153    handle_completion_request_with_insert_and_replace(
12154        &mut cx,
12155        completion_marked_buffer,
12156        vec![(completion_text, completion_text)],
12157        Arc::new(AtomicUsize::new(0)),
12158    )
12159    .await;
12160    cx.condition(|editor, _| editor.context_menu_visible())
12161        .await;
12162    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12163        editor
12164            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12165            .unwrap()
12166    });
12167    cx.assert_editor_state(expected);
12168    handle_resolve_completion_request(&mut cx, None).await;
12169    apply_additional_edits.await.unwrap();
12170
12171    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12172    let completion_text = "foo_and_bar";
12173    let initial_state = indoc! {"
12174        1. ooanbˇ
12175        2. zooanbˇ
12176        3. ooanbˇz
12177        4. zooanbˇz
12178        5. ooanˇ
12179        6. oanbˇ
12180
12181        ooanbˇ
12182    "};
12183    let completion_marked_buffer = indoc! {"
12184        1. ooanb
12185        2. zooanb
12186        3. ooanbz
12187        4. zooanbz
12188        5. ooan
12189        6. oanb
12190
12191        <ooanb|>
12192    "};
12193    let expected = indoc! {"
12194        1. foo_and_barˇ
12195        2. zfoo_and_barˇ
12196        3. foo_and_barˇz
12197        4. zfoo_and_barˇz
12198        5. ooanfoo_and_barˇ
12199        6. oanbfoo_and_barˇ
12200
12201        foo_and_barˇ
12202    "};
12203    cx.set_state(initial_state);
12204    cx.update_editor(|editor, window, cx| {
12205        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12206    });
12207    handle_completion_request_with_insert_and_replace(
12208        &mut cx,
12209        completion_marked_buffer,
12210        vec![(completion_text, completion_text)],
12211        Arc::new(AtomicUsize::new(0)),
12212    )
12213    .await;
12214    cx.condition(|editor, _| editor.context_menu_visible())
12215        .await;
12216    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12217        editor
12218            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12219            .unwrap()
12220    });
12221    cx.assert_editor_state(expected);
12222    handle_resolve_completion_request(&mut cx, None).await;
12223    apply_additional_edits.await.unwrap();
12224
12225    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12226    // (expects the same as if it was inserted at the end)
12227    let completion_text = "foo_and_bar";
12228    let initial_state = indoc! {"
12229        1. ooˇanb
12230        2. zooˇanb
12231        3. ooˇanbz
12232        4. zooˇanbz
12233
12234        ooˇanb
12235    "};
12236    let completion_marked_buffer = indoc! {"
12237        1. ooanb
12238        2. zooanb
12239        3. ooanbz
12240        4. zooanbz
12241
12242        <oo|anb>
12243    "};
12244    let expected = indoc! {"
12245        1. foo_and_barˇ
12246        2. zfoo_and_barˇ
12247        3. foo_and_barˇz
12248        4. zfoo_and_barˇz
12249
12250        foo_and_barˇ
12251    "};
12252    cx.set_state(initial_state);
12253    cx.update_editor(|editor, window, cx| {
12254        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12255    });
12256    handle_completion_request_with_insert_and_replace(
12257        &mut cx,
12258        completion_marked_buffer,
12259        vec![(completion_text, completion_text)],
12260        Arc::new(AtomicUsize::new(0)),
12261    )
12262    .await;
12263    cx.condition(|editor, _| editor.context_menu_visible())
12264        .await;
12265    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12266        editor
12267            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12268            .unwrap()
12269    });
12270    cx.assert_editor_state(expected);
12271    handle_resolve_completion_request(&mut cx, None).await;
12272    apply_additional_edits.await.unwrap();
12273}
12274
12275// This used to crash
12276#[gpui::test]
12277async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12278    init_test(cx, |_| {});
12279
12280    let buffer_text = indoc! {"
12281        fn main() {
12282            10.satu;
12283
12284            //
12285            // separate cursors so they open in different excerpts (manually reproducible)
12286            //
12287
12288            10.satu20;
12289        }
12290    "};
12291    let multibuffer_text_with_selections = indoc! {"
12292        fn main() {
12293            10.satuˇ;
12294
12295            //
12296
12297            //
12298
12299            10.satuˇ20;
12300        }
12301    "};
12302    let expected_multibuffer = indoc! {"
12303        fn main() {
12304            10.saturating_sub()ˇ;
12305
12306            //
12307
12308            //
12309
12310            10.saturating_sub()ˇ;
12311        }
12312    "};
12313
12314    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12315    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12316
12317    let fs = FakeFs::new(cx.executor());
12318    fs.insert_tree(
12319        path!("/a"),
12320        json!({
12321            "main.rs": buffer_text,
12322        }),
12323    )
12324    .await;
12325
12326    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12327    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12328    language_registry.add(rust_lang());
12329    let mut fake_servers = language_registry.register_fake_lsp(
12330        "Rust",
12331        FakeLspAdapter {
12332            capabilities: lsp::ServerCapabilities {
12333                completion_provider: Some(lsp::CompletionOptions {
12334                    resolve_provider: None,
12335                    ..lsp::CompletionOptions::default()
12336                }),
12337                ..lsp::ServerCapabilities::default()
12338            },
12339            ..FakeLspAdapter::default()
12340        },
12341    );
12342    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12343    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12344    let buffer = project
12345        .update(cx, |project, cx| {
12346            project.open_local_buffer(path!("/a/main.rs"), cx)
12347        })
12348        .await
12349        .unwrap();
12350
12351    let multi_buffer = cx.new(|cx| {
12352        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12353        multi_buffer.push_excerpts(
12354            buffer.clone(),
12355            [ExcerptRange::new(0..first_excerpt_end)],
12356            cx,
12357        );
12358        multi_buffer.push_excerpts(
12359            buffer.clone(),
12360            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12361            cx,
12362        );
12363        multi_buffer
12364    });
12365
12366    let editor = workspace
12367        .update(cx, |_, window, cx| {
12368            cx.new(|cx| {
12369                Editor::new(
12370                    EditorMode::Full {
12371                        scale_ui_elements_with_buffer_font_size: false,
12372                        show_active_line_background: false,
12373                        sized_by_content: false,
12374                    },
12375                    multi_buffer.clone(),
12376                    Some(project.clone()),
12377                    window,
12378                    cx,
12379                )
12380            })
12381        })
12382        .unwrap();
12383
12384    let pane = workspace
12385        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12386        .unwrap();
12387    pane.update_in(cx, |pane, window, cx| {
12388        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12389    });
12390
12391    let fake_server = fake_servers.next().await.unwrap();
12392
12393    editor.update_in(cx, |editor, window, cx| {
12394        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12395            s.select_ranges([
12396                Point::new(1, 11)..Point::new(1, 11),
12397                Point::new(7, 11)..Point::new(7, 11),
12398            ])
12399        });
12400
12401        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12402    });
12403
12404    editor.update_in(cx, |editor, window, cx| {
12405        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12406    });
12407
12408    fake_server
12409        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12410            let completion_item = lsp::CompletionItem {
12411                label: "saturating_sub()".into(),
12412                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12413                    lsp::InsertReplaceEdit {
12414                        new_text: "saturating_sub()".to_owned(),
12415                        insert: lsp::Range::new(
12416                            lsp::Position::new(7, 7),
12417                            lsp::Position::new(7, 11),
12418                        ),
12419                        replace: lsp::Range::new(
12420                            lsp::Position::new(7, 7),
12421                            lsp::Position::new(7, 13),
12422                        ),
12423                    },
12424                )),
12425                ..lsp::CompletionItem::default()
12426            };
12427
12428            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12429        })
12430        .next()
12431        .await
12432        .unwrap();
12433
12434    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12435        .await;
12436
12437    editor
12438        .update_in(cx, |editor, window, cx| {
12439            editor
12440                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12441                .unwrap()
12442        })
12443        .await
12444        .unwrap();
12445
12446    editor.update(cx, |editor, cx| {
12447        assert_text_with_selections(editor, expected_multibuffer, cx);
12448    })
12449}
12450
12451#[gpui::test]
12452async fn test_completion(cx: &mut TestAppContext) {
12453    init_test(cx, |_| {});
12454
12455    let mut cx = EditorLspTestContext::new_rust(
12456        lsp::ServerCapabilities {
12457            completion_provider: Some(lsp::CompletionOptions {
12458                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12459                resolve_provider: Some(true),
12460                ..Default::default()
12461            }),
12462            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12463            ..Default::default()
12464        },
12465        cx,
12466    )
12467    .await;
12468    let counter = Arc::new(AtomicUsize::new(0));
12469
12470    cx.set_state(indoc! {"
12471        oneˇ
12472        two
12473        three
12474    "});
12475    cx.simulate_keystroke(".");
12476    handle_completion_request(
12477        indoc! {"
12478            one.|<>
12479            two
12480            three
12481        "},
12482        vec!["first_completion", "second_completion"],
12483        true,
12484        counter.clone(),
12485        &mut cx,
12486    )
12487    .await;
12488    cx.condition(|editor, _| editor.context_menu_visible())
12489        .await;
12490    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12491
12492    let _handler = handle_signature_help_request(
12493        &mut cx,
12494        lsp::SignatureHelp {
12495            signatures: vec![lsp::SignatureInformation {
12496                label: "test signature".to_string(),
12497                documentation: None,
12498                parameters: Some(vec![lsp::ParameterInformation {
12499                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12500                    documentation: None,
12501                }]),
12502                active_parameter: None,
12503            }],
12504            active_signature: None,
12505            active_parameter: None,
12506        },
12507    );
12508    cx.update_editor(|editor, window, cx| {
12509        assert!(
12510            !editor.signature_help_state.is_shown(),
12511            "No signature help was called for"
12512        );
12513        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12514    });
12515    cx.run_until_parked();
12516    cx.update_editor(|editor, _, _| {
12517        assert!(
12518            !editor.signature_help_state.is_shown(),
12519            "No signature help should be shown when completions menu is open"
12520        );
12521    });
12522
12523    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12524        editor.context_menu_next(&Default::default(), window, cx);
12525        editor
12526            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12527            .unwrap()
12528    });
12529    cx.assert_editor_state(indoc! {"
12530        one.second_completionˇ
12531        two
12532        three
12533    "});
12534
12535    handle_resolve_completion_request(
12536        &mut cx,
12537        Some(vec![
12538            (
12539                //This overlaps with the primary completion edit which is
12540                //misbehavior from the LSP spec, test that we filter it out
12541                indoc! {"
12542                    one.second_ˇcompletion
12543                    two
12544                    threeˇ
12545                "},
12546                "overlapping additional edit",
12547            ),
12548            (
12549                indoc! {"
12550                    one.second_completion
12551                    two
12552                    threeˇ
12553                "},
12554                "\nadditional edit",
12555            ),
12556        ]),
12557    )
12558    .await;
12559    apply_additional_edits.await.unwrap();
12560    cx.assert_editor_state(indoc! {"
12561        one.second_completionˇ
12562        two
12563        three
12564        additional edit
12565    "});
12566
12567    cx.set_state(indoc! {"
12568        one.second_completion
12569        twoˇ
12570        threeˇ
12571        additional edit
12572    "});
12573    cx.simulate_keystroke(" ");
12574    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12575    cx.simulate_keystroke("s");
12576    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12577
12578    cx.assert_editor_state(indoc! {"
12579        one.second_completion
12580        two sˇ
12581        three sˇ
12582        additional edit
12583    "});
12584    handle_completion_request(
12585        indoc! {"
12586            one.second_completion
12587            two s
12588            three <s|>
12589            additional edit
12590        "},
12591        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12592        true,
12593        counter.clone(),
12594        &mut cx,
12595    )
12596    .await;
12597    cx.condition(|editor, _| editor.context_menu_visible())
12598        .await;
12599    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12600
12601    cx.simulate_keystroke("i");
12602
12603    handle_completion_request(
12604        indoc! {"
12605            one.second_completion
12606            two si
12607            three <si|>
12608            additional edit
12609        "},
12610        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12611        true,
12612        counter.clone(),
12613        &mut cx,
12614    )
12615    .await;
12616    cx.condition(|editor, _| editor.context_menu_visible())
12617        .await;
12618    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12619
12620    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12621        editor
12622            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12623            .unwrap()
12624    });
12625    cx.assert_editor_state(indoc! {"
12626        one.second_completion
12627        two sixth_completionˇ
12628        three sixth_completionˇ
12629        additional edit
12630    "});
12631
12632    apply_additional_edits.await.unwrap();
12633
12634    update_test_language_settings(&mut cx, |settings| {
12635        settings.defaults.show_completions_on_input = Some(false);
12636    });
12637    cx.set_state("editorˇ");
12638    cx.simulate_keystroke(".");
12639    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12640    cx.simulate_keystrokes("c l o");
12641    cx.assert_editor_state("editor.cloˇ");
12642    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12643    cx.update_editor(|editor, window, cx| {
12644        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12645    });
12646    handle_completion_request(
12647        "editor.<clo|>",
12648        vec!["close", "clobber"],
12649        true,
12650        counter.clone(),
12651        &mut cx,
12652    )
12653    .await;
12654    cx.condition(|editor, _| editor.context_menu_visible())
12655        .await;
12656    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12657
12658    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12659        editor
12660            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12661            .unwrap()
12662    });
12663    cx.assert_editor_state("editor.clobberˇ");
12664    handle_resolve_completion_request(&mut cx, None).await;
12665    apply_additional_edits.await.unwrap();
12666}
12667
12668#[gpui::test]
12669async fn test_completion_reuse(cx: &mut TestAppContext) {
12670    init_test(cx, |_| {});
12671
12672    let mut cx = EditorLspTestContext::new_rust(
12673        lsp::ServerCapabilities {
12674            completion_provider: Some(lsp::CompletionOptions {
12675                trigger_characters: Some(vec![".".to_string()]),
12676                ..Default::default()
12677            }),
12678            ..Default::default()
12679        },
12680        cx,
12681    )
12682    .await;
12683
12684    let counter = Arc::new(AtomicUsize::new(0));
12685    cx.set_state("objˇ");
12686    cx.simulate_keystroke(".");
12687
12688    // Initial completion request returns complete results
12689    let is_incomplete = false;
12690    handle_completion_request(
12691        "obj.|<>",
12692        vec!["a", "ab", "abc"],
12693        is_incomplete,
12694        counter.clone(),
12695        &mut cx,
12696    )
12697    .await;
12698    cx.run_until_parked();
12699    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12700    cx.assert_editor_state("obj.ˇ");
12701    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12702
12703    // Type "a" - filters existing completions
12704    cx.simulate_keystroke("a");
12705    cx.run_until_parked();
12706    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12707    cx.assert_editor_state("obj.aˇ");
12708    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12709
12710    // Type "b" - filters existing completions
12711    cx.simulate_keystroke("b");
12712    cx.run_until_parked();
12713    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12714    cx.assert_editor_state("obj.abˇ");
12715    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12716
12717    // Type "c" - filters existing completions
12718    cx.simulate_keystroke("c");
12719    cx.run_until_parked();
12720    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12721    cx.assert_editor_state("obj.abcˇ");
12722    check_displayed_completions(vec!["abc"], &mut cx);
12723
12724    // Backspace to delete "c" - filters existing completions
12725    cx.update_editor(|editor, window, cx| {
12726        editor.backspace(&Backspace, window, cx);
12727    });
12728    cx.run_until_parked();
12729    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12730    cx.assert_editor_state("obj.abˇ");
12731    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12732
12733    // Moving cursor to the left dismisses menu.
12734    cx.update_editor(|editor, window, cx| {
12735        editor.move_left(&MoveLeft, window, cx);
12736    });
12737    cx.run_until_parked();
12738    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12739    cx.assert_editor_state("obj.aˇb");
12740    cx.update_editor(|editor, _, _| {
12741        assert_eq!(editor.context_menu_visible(), false);
12742    });
12743
12744    // Type "b" - new request
12745    cx.simulate_keystroke("b");
12746    let is_incomplete = false;
12747    handle_completion_request(
12748        "obj.<ab|>a",
12749        vec!["ab", "abc"],
12750        is_incomplete,
12751        counter.clone(),
12752        &mut cx,
12753    )
12754    .await;
12755    cx.run_until_parked();
12756    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12757    cx.assert_editor_state("obj.abˇb");
12758    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12759
12760    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12761    cx.update_editor(|editor, window, cx| {
12762        editor.backspace(&Backspace, window, cx);
12763    });
12764    let is_incomplete = false;
12765    handle_completion_request(
12766        "obj.<a|>b",
12767        vec!["a", "ab", "abc"],
12768        is_incomplete,
12769        counter.clone(),
12770        &mut cx,
12771    )
12772    .await;
12773    cx.run_until_parked();
12774    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12775    cx.assert_editor_state("obj.aˇb");
12776    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12777
12778    // Backspace to delete "a" - dismisses menu.
12779    cx.update_editor(|editor, window, cx| {
12780        editor.backspace(&Backspace, window, cx);
12781    });
12782    cx.run_until_parked();
12783    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12784    cx.assert_editor_state("obj.ˇb");
12785    cx.update_editor(|editor, _, _| {
12786        assert_eq!(editor.context_menu_visible(), false);
12787    });
12788}
12789
12790#[gpui::test]
12791async fn test_word_completion(cx: &mut TestAppContext) {
12792    let lsp_fetch_timeout_ms = 10;
12793    init_test(cx, |language_settings| {
12794        language_settings.defaults.completions = Some(CompletionSettings {
12795            words: WordsCompletionMode::Fallback,
12796            lsp: true,
12797            lsp_fetch_timeout_ms: 10,
12798            lsp_insert_mode: LspInsertMode::Insert,
12799        });
12800    });
12801
12802    let mut cx = EditorLspTestContext::new_rust(
12803        lsp::ServerCapabilities {
12804            completion_provider: Some(lsp::CompletionOptions {
12805                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12806                ..lsp::CompletionOptions::default()
12807            }),
12808            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12809            ..lsp::ServerCapabilities::default()
12810        },
12811        cx,
12812    )
12813    .await;
12814
12815    let throttle_completions = Arc::new(AtomicBool::new(false));
12816
12817    let lsp_throttle_completions = throttle_completions.clone();
12818    let _completion_requests_handler =
12819        cx.lsp
12820            .server
12821            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12822                let lsp_throttle_completions = lsp_throttle_completions.clone();
12823                let cx = cx.clone();
12824                async move {
12825                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12826                        cx.background_executor()
12827                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12828                            .await;
12829                    }
12830                    Ok(Some(lsp::CompletionResponse::Array(vec![
12831                        lsp::CompletionItem {
12832                            label: "first".into(),
12833                            ..lsp::CompletionItem::default()
12834                        },
12835                        lsp::CompletionItem {
12836                            label: "last".into(),
12837                            ..lsp::CompletionItem::default()
12838                        },
12839                    ])))
12840                }
12841            });
12842
12843    cx.set_state(indoc! {"
12844        oneˇ
12845        two
12846        three
12847    "});
12848    cx.simulate_keystroke(".");
12849    cx.executor().run_until_parked();
12850    cx.condition(|editor, _| editor.context_menu_visible())
12851        .await;
12852    cx.update_editor(|editor, window, cx| {
12853        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12854        {
12855            assert_eq!(
12856                completion_menu_entries(&menu),
12857                &["first", "last"],
12858                "When LSP server is fast to reply, no fallback word completions are used"
12859            );
12860        } else {
12861            panic!("expected completion menu to be open");
12862        }
12863        editor.cancel(&Cancel, window, cx);
12864    });
12865    cx.executor().run_until_parked();
12866    cx.condition(|editor, _| !editor.context_menu_visible())
12867        .await;
12868
12869    throttle_completions.store(true, atomic::Ordering::Release);
12870    cx.simulate_keystroke(".");
12871    cx.executor()
12872        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12873    cx.executor().run_until_parked();
12874    cx.condition(|editor, _| editor.context_menu_visible())
12875        .await;
12876    cx.update_editor(|editor, _, _| {
12877        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12878        {
12879            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12880                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12881        } else {
12882            panic!("expected completion menu to be open");
12883        }
12884    });
12885}
12886
12887#[gpui::test]
12888async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12889    init_test(cx, |language_settings| {
12890        language_settings.defaults.completions = Some(CompletionSettings {
12891            words: WordsCompletionMode::Enabled,
12892            lsp: true,
12893            lsp_fetch_timeout_ms: 0,
12894            lsp_insert_mode: LspInsertMode::Insert,
12895        });
12896    });
12897
12898    let mut cx = EditorLspTestContext::new_rust(
12899        lsp::ServerCapabilities {
12900            completion_provider: Some(lsp::CompletionOptions {
12901                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12902                ..lsp::CompletionOptions::default()
12903            }),
12904            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12905            ..lsp::ServerCapabilities::default()
12906        },
12907        cx,
12908    )
12909    .await;
12910
12911    let _completion_requests_handler =
12912        cx.lsp
12913            .server
12914            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12915                Ok(Some(lsp::CompletionResponse::Array(vec![
12916                    lsp::CompletionItem {
12917                        label: "first".into(),
12918                        ..lsp::CompletionItem::default()
12919                    },
12920                    lsp::CompletionItem {
12921                        label: "last".into(),
12922                        ..lsp::CompletionItem::default()
12923                    },
12924                ])))
12925            });
12926
12927    cx.set_state(indoc! {"ˇ
12928        first
12929        last
12930        second
12931    "});
12932    cx.simulate_keystroke(".");
12933    cx.executor().run_until_parked();
12934    cx.condition(|editor, _| editor.context_menu_visible())
12935        .await;
12936    cx.update_editor(|editor, _, _| {
12937        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12938        {
12939            assert_eq!(
12940                completion_menu_entries(&menu),
12941                &["first", "last", "second"],
12942                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12943            );
12944        } else {
12945            panic!("expected completion menu to be open");
12946        }
12947    });
12948}
12949
12950#[gpui::test]
12951async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12952    init_test(cx, |language_settings| {
12953        language_settings.defaults.completions = Some(CompletionSettings {
12954            words: WordsCompletionMode::Disabled,
12955            lsp: true,
12956            lsp_fetch_timeout_ms: 0,
12957            lsp_insert_mode: LspInsertMode::Insert,
12958        });
12959    });
12960
12961    let mut cx = EditorLspTestContext::new_rust(
12962        lsp::ServerCapabilities {
12963            completion_provider: Some(lsp::CompletionOptions {
12964                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12965                ..lsp::CompletionOptions::default()
12966            }),
12967            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12968            ..lsp::ServerCapabilities::default()
12969        },
12970        cx,
12971    )
12972    .await;
12973
12974    let _completion_requests_handler =
12975        cx.lsp
12976            .server
12977            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12978                panic!("LSP completions should not be queried when dealing with word completions")
12979            });
12980
12981    cx.set_state(indoc! {"ˇ
12982        first
12983        last
12984        second
12985    "});
12986    cx.update_editor(|editor, window, cx| {
12987        editor.show_word_completions(&ShowWordCompletions, window, cx);
12988    });
12989    cx.executor().run_until_parked();
12990    cx.condition(|editor, _| editor.context_menu_visible())
12991        .await;
12992    cx.update_editor(|editor, _, _| {
12993        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12994        {
12995            assert_eq!(
12996                completion_menu_entries(&menu),
12997                &["first", "last", "second"],
12998                "`ShowWordCompletions` action should show word completions"
12999            );
13000        } else {
13001            panic!("expected completion menu to be open");
13002        }
13003    });
13004
13005    cx.simulate_keystroke("l");
13006    cx.executor().run_until_parked();
13007    cx.condition(|editor, _| editor.context_menu_visible())
13008        .await;
13009    cx.update_editor(|editor, _, _| {
13010        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13011        {
13012            assert_eq!(
13013                completion_menu_entries(&menu),
13014                &["last"],
13015                "After showing word completions, further editing should filter them and not query the LSP"
13016            );
13017        } else {
13018            panic!("expected completion menu to be open");
13019        }
13020    });
13021}
13022
13023#[gpui::test]
13024async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13025    init_test(cx, |language_settings| {
13026        language_settings.defaults.completions = Some(CompletionSettings {
13027            words: WordsCompletionMode::Fallback,
13028            lsp: false,
13029            lsp_fetch_timeout_ms: 0,
13030            lsp_insert_mode: LspInsertMode::Insert,
13031        });
13032    });
13033
13034    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13035
13036    cx.set_state(indoc! {"ˇ
13037        0_usize
13038        let
13039        33
13040        4.5f32
13041    "});
13042    cx.update_editor(|editor, window, cx| {
13043        editor.show_completions(&ShowCompletions::default(), window, cx);
13044    });
13045    cx.executor().run_until_parked();
13046    cx.condition(|editor, _| editor.context_menu_visible())
13047        .await;
13048    cx.update_editor(|editor, window, cx| {
13049        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13050        {
13051            assert_eq!(
13052                completion_menu_entries(&menu),
13053                &["let"],
13054                "With no digits in the completion query, no digits should be in the word completions"
13055            );
13056        } else {
13057            panic!("expected completion menu to be open");
13058        }
13059        editor.cancel(&Cancel, window, cx);
13060    });
13061
13062    cx.set_state(indoc! {"13063        0_usize
13064        let
13065        3
13066        33.35f32
13067    "});
13068    cx.update_editor(|editor, window, cx| {
13069        editor.show_completions(&ShowCompletions::default(), window, cx);
13070    });
13071    cx.executor().run_until_parked();
13072    cx.condition(|editor, _| editor.context_menu_visible())
13073        .await;
13074    cx.update_editor(|editor, _, _| {
13075        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13076        {
13077            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13078                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13079        } else {
13080            panic!("expected completion menu to be open");
13081        }
13082    });
13083}
13084
13085fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13086    let position = || lsp::Position {
13087        line: params.text_document_position.position.line,
13088        character: params.text_document_position.position.character,
13089    };
13090    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13091        range: lsp::Range {
13092            start: position(),
13093            end: position(),
13094        },
13095        new_text: text.to_string(),
13096    }))
13097}
13098
13099#[gpui::test]
13100async fn test_multiline_completion(cx: &mut TestAppContext) {
13101    init_test(cx, |_| {});
13102
13103    let fs = FakeFs::new(cx.executor());
13104    fs.insert_tree(
13105        path!("/a"),
13106        json!({
13107            "main.ts": "a",
13108        }),
13109    )
13110    .await;
13111
13112    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13113    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13114    let typescript_language = Arc::new(Language::new(
13115        LanguageConfig {
13116            name: "TypeScript".into(),
13117            matcher: LanguageMatcher {
13118                path_suffixes: vec!["ts".to_string()],
13119                ..LanguageMatcher::default()
13120            },
13121            line_comments: vec!["// ".into()],
13122            ..LanguageConfig::default()
13123        },
13124        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13125    ));
13126    language_registry.add(typescript_language.clone());
13127    let mut fake_servers = language_registry.register_fake_lsp(
13128        "TypeScript",
13129        FakeLspAdapter {
13130            capabilities: lsp::ServerCapabilities {
13131                completion_provider: Some(lsp::CompletionOptions {
13132                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13133                    ..lsp::CompletionOptions::default()
13134                }),
13135                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13136                ..lsp::ServerCapabilities::default()
13137            },
13138            // Emulate vtsls label generation
13139            label_for_completion: Some(Box::new(|item, _| {
13140                let text = if let Some(description) = item
13141                    .label_details
13142                    .as_ref()
13143                    .and_then(|label_details| label_details.description.as_ref())
13144                {
13145                    format!("{} {}", item.label, description)
13146                } else if let Some(detail) = &item.detail {
13147                    format!("{} {}", item.label, detail)
13148                } else {
13149                    item.label.clone()
13150                };
13151                let len = text.len();
13152                Some(language::CodeLabel {
13153                    text,
13154                    runs: Vec::new(),
13155                    filter_range: 0..len,
13156                })
13157            })),
13158            ..FakeLspAdapter::default()
13159        },
13160    );
13161    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13162    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13163    let worktree_id = workspace
13164        .update(cx, |workspace, _window, cx| {
13165            workspace.project().update(cx, |project, cx| {
13166                project.worktrees(cx).next().unwrap().read(cx).id()
13167            })
13168        })
13169        .unwrap();
13170    let _buffer = project
13171        .update(cx, |project, cx| {
13172            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13173        })
13174        .await
13175        .unwrap();
13176    let editor = workspace
13177        .update(cx, |workspace, window, cx| {
13178            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13179        })
13180        .unwrap()
13181        .await
13182        .unwrap()
13183        .downcast::<Editor>()
13184        .unwrap();
13185    let fake_server = fake_servers.next().await.unwrap();
13186
13187    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13188    let multiline_label_2 = "a\nb\nc\n";
13189    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13190    let multiline_description = "d\ne\nf\n";
13191    let multiline_detail_2 = "g\nh\ni\n";
13192
13193    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13194        move |params, _| async move {
13195            Ok(Some(lsp::CompletionResponse::Array(vec![
13196                lsp::CompletionItem {
13197                    label: multiline_label.to_string(),
13198                    text_edit: gen_text_edit(&params, "new_text_1"),
13199                    ..lsp::CompletionItem::default()
13200                },
13201                lsp::CompletionItem {
13202                    label: "single line label 1".to_string(),
13203                    detail: Some(multiline_detail.to_string()),
13204                    text_edit: gen_text_edit(&params, "new_text_2"),
13205                    ..lsp::CompletionItem::default()
13206                },
13207                lsp::CompletionItem {
13208                    label: "single line label 2".to_string(),
13209                    label_details: Some(lsp::CompletionItemLabelDetails {
13210                        description: Some(multiline_description.to_string()),
13211                        detail: None,
13212                    }),
13213                    text_edit: gen_text_edit(&params, "new_text_2"),
13214                    ..lsp::CompletionItem::default()
13215                },
13216                lsp::CompletionItem {
13217                    label: multiline_label_2.to_string(),
13218                    detail: Some(multiline_detail_2.to_string()),
13219                    text_edit: gen_text_edit(&params, "new_text_3"),
13220                    ..lsp::CompletionItem::default()
13221                },
13222                lsp::CompletionItem {
13223                    label: "Label with many     spaces and \t but without newlines".to_string(),
13224                    detail: Some(
13225                        "Details with many     spaces and \t but without newlines".to_string(),
13226                    ),
13227                    text_edit: gen_text_edit(&params, "new_text_4"),
13228                    ..lsp::CompletionItem::default()
13229                },
13230            ])))
13231        },
13232    );
13233
13234    editor.update_in(cx, |editor, window, cx| {
13235        cx.focus_self(window);
13236        editor.move_to_end(&MoveToEnd, window, cx);
13237        editor.handle_input(".", window, cx);
13238    });
13239    cx.run_until_parked();
13240    completion_handle.next().await.unwrap();
13241
13242    editor.update(cx, |editor, _| {
13243        assert!(editor.context_menu_visible());
13244        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13245        {
13246            let completion_labels = menu
13247                .completions
13248                .borrow()
13249                .iter()
13250                .map(|c| c.label.text.clone())
13251                .collect::<Vec<_>>();
13252            assert_eq!(
13253                completion_labels,
13254                &[
13255                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13256                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13257                    "single line label 2 d e f ",
13258                    "a b c g h i ",
13259                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13260                ],
13261                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13262            );
13263
13264            for completion in menu
13265                .completions
13266                .borrow()
13267                .iter() {
13268                    assert_eq!(
13269                        completion.label.filter_range,
13270                        0..completion.label.text.len(),
13271                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13272                    );
13273                }
13274        } else {
13275            panic!("expected completion menu to be open");
13276        }
13277    });
13278}
13279
13280#[gpui::test]
13281async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13282    init_test(cx, |_| {});
13283    let mut cx = EditorLspTestContext::new_rust(
13284        lsp::ServerCapabilities {
13285            completion_provider: Some(lsp::CompletionOptions {
13286                trigger_characters: Some(vec![".".to_string()]),
13287                ..Default::default()
13288            }),
13289            ..Default::default()
13290        },
13291        cx,
13292    )
13293    .await;
13294    cx.lsp
13295        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13296            Ok(Some(lsp::CompletionResponse::Array(vec![
13297                lsp::CompletionItem {
13298                    label: "first".into(),
13299                    ..Default::default()
13300                },
13301                lsp::CompletionItem {
13302                    label: "last".into(),
13303                    ..Default::default()
13304                },
13305            ])))
13306        });
13307    cx.set_state("variableˇ");
13308    cx.simulate_keystroke(".");
13309    cx.executor().run_until_parked();
13310
13311    cx.update_editor(|editor, _, _| {
13312        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13313        {
13314            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13315        } else {
13316            panic!("expected completion menu to be open");
13317        }
13318    });
13319
13320    cx.update_editor(|editor, window, cx| {
13321        editor.move_page_down(&MovePageDown::default(), window, cx);
13322        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13323        {
13324            assert!(
13325                menu.selected_item == 1,
13326                "expected PageDown to select the last item from the context menu"
13327            );
13328        } else {
13329            panic!("expected completion menu to stay open after PageDown");
13330        }
13331    });
13332
13333    cx.update_editor(|editor, window, cx| {
13334        editor.move_page_up(&MovePageUp::default(), window, cx);
13335        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13336        {
13337            assert!(
13338                menu.selected_item == 0,
13339                "expected PageUp to select the first item from the context menu"
13340            );
13341        } else {
13342            panic!("expected completion menu to stay open after PageUp");
13343        }
13344    });
13345}
13346
13347#[gpui::test]
13348async fn test_as_is_completions(cx: &mut TestAppContext) {
13349    init_test(cx, |_| {});
13350    let mut cx = EditorLspTestContext::new_rust(
13351        lsp::ServerCapabilities {
13352            completion_provider: Some(lsp::CompletionOptions {
13353                ..Default::default()
13354            }),
13355            ..Default::default()
13356        },
13357        cx,
13358    )
13359    .await;
13360    cx.lsp
13361        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13362            Ok(Some(lsp::CompletionResponse::Array(vec![
13363                lsp::CompletionItem {
13364                    label: "unsafe".into(),
13365                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13366                        range: lsp::Range {
13367                            start: lsp::Position {
13368                                line: 1,
13369                                character: 2,
13370                            },
13371                            end: lsp::Position {
13372                                line: 1,
13373                                character: 3,
13374                            },
13375                        },
13376                        new_text: "unsafe".to_string(),
13377                    })),
13378                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13379                    ..Default::default()
13380                },
13381            ])))
13382        });
13383    cx.set_state("fn a() {}\n");
13384    cx.executor().run_until_parked();
13385    cx.update_editor(|editor, window, cx| {
13386        editor.show_completions(
13387            &ShowCompletions {
13388                trigger: Some("\n".into()),
13389            },
13390            window,
13391            cx,
13392        );
13393    });
13394    cx.executor().run_until_parked();
13395
13396    cx.update_editor(|editor, window, cx| {
13397        editor.confirm_completion(&Default::default(), window, cx)
13398    });
13399    cx.executor().run_until_parked();
13400    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13401}
13402
13403#[gpui::test]
13404async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13405    init_test(cx, |_| {});
13406
13407    let mut cx = EditorLspTestContext::new_rust(
13408        lsp::ServerCapabilities {
13409            completion_provider: Some(lsp::CompletionOptions {
13410                trigger_characters: Some(vec![".".to_string()]),
13411                resolve_provider: Some(true),
13412                ..Default::default()
13413            }),
13414            ..Default::default()
13415        },
13416        cx,
13417    )
13418    .await;
13419
13420    cx.set_state("fn main() { let a = 2ˇ; }");
13421    cx.simulate_keystroke(".");
13422    let completion_item = lsp::CompletionItem {
13423        label: "Some".into(),
13424        kind: Some(lsp::CompletionItemKind::SNIPPET),
13425        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13426        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13427            kind: lsp::MarkupKind::Markdown,
13428            value: "```rust\nSome(2)\n```".to_string(),
13429        })),
13430        deprecated: Some(false),
13431        sort_text: Some("Some".to_string()),
13432        filter_text: Some("Some".to_string()),
13433        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13434        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13435            range: lsp::Range {
13436                start: lsp::Position {
13437                    line: 0,
13438                    character: 22,
13439                },
13440                end: lsp::Position {
13441                    line: 0,
13442                    character: 22,
13443                },
13444            },
13445            new_text: "Some(2)".to_string(),
13446        })),
13447        additional_text_edits: Some(vec![lsp::TextEdit {
13448            range: lsp::Range {
13449                start: lsp::Position {
13450                    line: 0,
13451                    character: 20,
13452                },
13453                end: lsp::Position {
13454                    line: 0,
13455                    character: 22,
13456                },
13457            },
13458            new_text: "".to_string(),
13459        }]),
13460        ..Default::default()
13461    };
13462
13463    let closure_completion_item = completion_item.clone();
13464    let counter = Arc::new(AtomicUsize::new(0));
13465    let counter_clone = counter.clone();
13466    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13467        let task_completion_item = closure_completion_item.clone();
13468        counter_clone.fetch_add(1, atomic::Ordering::Release);
13469        async move {
13470            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13471                is_incomplete: true,
13472                item_defaults: None,
13473                items: vec![task_completion_item],
13474            })))
13475        }
13476    });
13477
13478    cx.condition(|editor, _| editor.context_menu_visible())
13479        .await;
13480    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13481    assert!(request.next().await.is_some());
13482    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13483
13484    cx.simulate_keystrokes("S o m");
13485    cx.condition(|editor, _| editor.context_menu_visible())
13486        .await;
13487    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13488    assert!(request.next().await.is_some());
13489    assert!(request.next().await.is_some());
13490    assert!(request.next().await.is_some());
13491    request.close();
13492    assert!(request.next().await.is_none());
13493    assert_eq!(
13494        counter.load(atomic::Ordering::Acquire),
13495        4,
13496        "With the completions menu open, only one LSP request should happen per input"
13497    );
13498}
13499
13500#[gpui::test]
13501async fn test_toggle_comment(cx: &mut TestAppContext) {
13502    init_test(cx, |_| {});
13503    let mut cx = EditorTestContext::new(cx).await;
13504    let language = Arc::new(Language::new(
13505        LanguageConfig {
13506            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13507            ..Default::default()
13508        },
13509        Some(tree_sitter_rust::LANGUAGE.into()),
13510    ));
13511    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13512
13513    // If multiple selections intersect a line, the line is only toggled once.
13514    cx.set_state(indoc! {"
13515        fn a() {
13516            «//b();
13517            ˇ»// «c();
13518            //ˇ»  d();
13519        }
13520    "});
13521
13522    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13523
13524    cx.assert_editor_state(indoc! {"
13525        fn a() {
13526            «b();
13527            c();
13528            ˇ» d();
13529        }
13530    "});
13531
13532    // The comment prefix is inserted at the same column for every line in a
13533    // selection.
13534    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13535
13536    cx.assert_editor_state(indoc! {"
13537        fn a() {
13538            // «b();
13539            // c();
13540            ˇ»//  d();
13541        }
13542    "});
13543
13544    // If a selection ends at the beginning of a line, that line is not toggled.
13545    cx.set_selections_state(indoc! {"
13546        fn a() {
13547            // b();
13548            «// c();
13549        ˇ»    //  d();
13550        }
13551    "});
13552
13553    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13554
13555    cx.assert_editor_state(indoc! {"
13556        fn a() {
13557            // b();
13558            «c();
13559        ˇ»    //  d();
13560        }
13561    "});
13562
13563    // If a selection span a single line and is empty, the line is toggled.
13564    cx.set_state(indoc! {"
13565        fn a() {
13566            a();
13567            b();
13568        ˇ
13569        }
13570    "});
13571
13572    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13573
13574    cx.assert_editor_state(indoc! {"
13575        fn a() {
13576            a();
13577            b();
13578        //•ˇ
13579        }
13580    "});
13581
13582    // If a selection span multiple lines, empty lines are not toggled.
13583    cx.set_state(indoc! {"
13584        fn a() {
13585            «a();
13586
13587            c();ˇ»
13588        }
13589    "});
13590
13591    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13592
13593    cx.assert_editor_state(indoc! {"
13594        fn a() {
13595            // «a();
13596
13597            // c();ˇ»
13598        }
13599    "});
13600
13601    // If a selection includes multiple comment prefixes, all lines are uncommented.
13602    cx.set_state(indoc! {"
13603        fn a() {
13604            «// a();
13605            /// b();
13606            //! c();ˇ»
13607        }
13608    "});
13609
13610    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13611
13612    cx.assert_editor_state(indoc! {"
13613        fn a() {
13614            «a();
13615            b();
13616            c();ˇ»
13617        }
13618    "});
13619}
13620
13621#[gpui::test]
13622async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13623    init_test(cx, |_| {});
13624    let mut cx = EditorTestContext::new(cx).await;
13625    let language = Arc::new(Language::new(
13626        LanguageConfig {
13627            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13628            ..Default::default()
13629        },
13630        Some(tree_sitter_rust::LANGUAGE.into()),
13631    ));
13632    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13633
13634    let toggle_comments = &ToggleComments {
13635        advance_downwards: false,
13636        ignore_indent: true,
13637    };
13638
13639    // If multiple selections intersect a line, the line is only toggled once.
13640    cx.set_state(indoc! {"
13641        fn a() {
13642        //    «b();
13643        //    c();
13644        //    ˇ» d();
13645        }
13646    "});
13647
13648    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13649
13650    cx.assert_editor_state(indoc! {"
13651        fn a() {
13652            «b();
13653            c();
13654            ˇ» d();
13655        }
13656    "});
13657
13658    // The comment prefix is inserted at the beginning of each line
13659    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13660
13661    cx.assert_editor_state(indoc! {"
13662        fn a() {
13663        //    «b();
13664        //    c();
13665        //    ˇ» d();
13666        }
13667    "});
13668
13669    // If a selection ends at the beginning of a line, that line is not toggled.
13670    cx.set_selections_state(indoc! {"
13671        fn a() {
13672        //    b();
13673        //    «c();
13674        ˇ»//     d();
13675        }
13676    "});
13677
13678    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13679
13680    cx.assert_editor_state(indoc! {"
13681        fn a() {
13682        //    b();
13683            «c();
13684        ˇ»//     d();
13685        }
13686    "});
13687
13688    // If a selection span a single line and is empty, the line is toggled.
13689    cx.set_state(indoc! {"
13690        fn a() {
13691            a();
13692            b();
13693        ˇ
13694        }
13695    "});
13696
13697    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13698
13699    cx.assert_editor_state(indoc! {"
13700        fn a() {
13701            a();
13702            b();
13703        //ˇ
13704        }
13705    "});
13706
13707    // If a selection span multiple lines, empty lines are not toggled.
13708    cx.set_state(indoc! {"
13709        fn a() {
13710            «a();
13711
13712            c();ˇ»
13713        }
13714    "});
13715
13716    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13717
13718    cx.assert_editor_state(indoc! {"
13719        fn a() {
13720        //    «a();
13721
13722        //    c();ˇ»
13723        }
13724    "});
13725
13726    // If a selection includes multiple comment prefixes, all lines are uncommented.
13727    cx.set_state(indoc! {"
13728        fn a() {
13729        //    «a();
13730        ///    b();
13731        //!    c();ˇ»
13732        }
13733    "});
13734
13735    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13736
13737    cx.assert_editor_state(indoc! {"
13738        fn a() {
13739            «a();
13740            b();
13741            c();ˇ»
13742        }
13743    "});
13744}
13745
13746#[gpui::test]
13747async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13748    init_test(cx, |_| {});
13749
13750    let language = Arc::new(Language::new(
13751        LanguageConfig {
13752            line_comments: vec!["// ".into()],
13753            ..Default::default()
13754        },
13755        Some(tree_sitter_rust::LANGUAGE.into()),
13756    ));
13757
13758    let mut cx = EditorTestContext::new(cx).await;
13759
13760    cx.language_registry().add(language.clone());
13761    cx.update_buffer(|buffer, cx| {
13762        buffer.set_language(Some(language), cx);
13763    });
13764
13765    let toggle_comments = &ToggleComments {
13766        advance_downwards: true,
13767        ignore_indent: false,
13768    };
13769
13770    // Single cursor on one line -> advance
13771    // Cursor moves horizontally 3 characters as well on non-blank line
13772    cx.set_state(indoc!(
13773        "fn a() {
13774             ˇdog();
13775             cat();
13776        }"
13777    ));
13778    cx.update_editor(|editor, window, cx| {
13779        editor.toggle_comments(toggle_comments, window, cx);
13780    });
13781    cx.assert_editor_state(indoc!(
13782        "fn a() {
13783             // dog();
13784             catˇ();
13785        }"
13786    ));
13787
13788    // Single selection on one line -> don't advance
13789    cx.set_state(indoc!(
13790        "fn a() {
13791             «dog()ˇ»;
13792             cat();
13793        }"
13794    ));
13795    cx.update_editor(|editor, window, cx| {
13796        editor.toggle_comments(toggle_comments, window, cx);
13797    });
13798    cx.assert_editor_state(indoc!(
13799        "fn a() {
13800             // «dog()ˇ»;
13801             cat();
13802        }"
13803    ));
13804
13805    // Multiple cursors on one line -> advance
13806    cx.set_state(indoc!(
13807        "fn a() {
13808             ˇdˇog();
13809             cat();
13810        }"
13811    ));
13812    cx.update_editor(|editor, window, cx| {
13813        editor.toggle_comments(toggle_comments, window, cx);
13814    });
13815    cx.assert_editor_state(indoc!(
13816        "fn a() {
13817             // dog();
13818             catˇ(ˇ);
13819        }"
13820    ));
13821
13822    // Multiple cursors on one line, with selection -> don't advance
13823    cx.set_state(indoc!(
13824        "fn a() {
13825             ˇdˇog«()ˇ»;
13826             cat();
13827        }"
13828    ));
13829    cx.update_editor(|editor, window, cx| {
13830        editor.toggle_comments(toggle_comments, window, cx);
13831    });
13832    cx.assert_editor_state(indoc!(
13833        "fn a() {
13834             // ˇdˇog«()ˇ»;
13835             cat();
13836        }"
13837    ));
13838
13839    // Single cursor on one line -> advance
13840    // Cursor moves to column 0 on blank line
13841    cx.set_state(indoc!(
13842        "fn a() {
13843             ˇdog();
13844
13845             cat();
13846        }"
13847    ));
13848    cx.update_editor(|editor, window, cx| {
13849        editor.toggle_comments(toggle_comments, window, cx);
13850    });
13851    cx.assert_editor_state(indoc!(
13852        "fn a() {
13853             // dog();
13854        ˇ
13855             cat();
13856        }"
13857    ));
13858
13859    // Single cursor on one line -> advance
13860    // Cursor starts and ends at column 0
13861    cx.set_state(indoc!(
13862        "fn a() {
13863         ˇ    dog();
13864             cat();
13865        }"
13866    ));
13867    cx.update_editor(|editor, window, cx| {
13868        editor.toggle_comments(toggle_comments, window, cx);
13869    });
13870    cx.assert_editor_state(indoc!(
13871        "fn a() {
13872             // dog();
13873         ˇ    cat();
13874        }"
13875    ));
13876}
13877
13878#[gpui::test]
13879async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13880    init_test(cx, |_| {});
13881
13882    let mut cx = EditorTestContext::new(cx).await;
13883
13884    let html_language = Arc::new(
13885        Language::new(
13886            LanguageConfig {
13887                name: "HTML".into(),
13888                block_comment: Some(BlockCommentConfig {
13889                    start: "<!-- ".into(),
13890                    prefix: "".into(),
13891                    end: " -->".into(),
13892                    tab_size: 0,
13893                }),
13894                ..Default::default()
13895            },
13896            Some(tree_sitter_html::LANGUAGE.into()),
13897        )
13898        .with_injection_query(
13899            r#"
13900            (script_element
13901                (raw_text) @injection.content
13902                (#set! injection.language "javascript"))
13903            "#,
13904        )
13905        .unwrap(),
13906    );
13907
13908    let javascript_language = Arc::new(Language::new(
13909        LanguageConfig {
13910            name: "JavaScript".into(),
13911            line_comments: vec!["// ".into()],
13912            ..Default::default()
13913        },
13914        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13915    ));
13916
13917    cx.language_registry().add(html_language.clone());
13918    cx.language_registry().add(javascript_language.clone());
13919    cx.update_buffer(|buffer, cx| {
13920        buffer.set_language(Some(html_language), cx);
13921    });
13922
13923    // Toggle comments for empty selections
13924    cx.set_state(
13925        &r#"
13926            <p>A</p>ˇ
13927            <p>B</p>ˇ
13928            <p>C</p>ˇ
13929        "#
13930        .unindent(),
13931    );
13932    cx.update_editor(|editor, window, cx| {
13933        editor.toggle_comments(&ToggleComments::default(), window, cx)
13934    });
13935    cx.assert_editor_state(
13936        &r#"
13937            <!-- <p>A</p>ˇ -->
13938            <!-- <p>B</p>ˇ -->
13939            <!-- <p>C</p>ˇ -->
13940        "#
13941        .unindent(),
13942    );
13943    cx.update_editor(|editor, window, cx| {
13944        editor.toggle_comments(&ToggleComments::default(), window, cx)
13945    });
13946    cx.assert_editor_state(
13947        &r#"
13948            <p>A</p>ˇ
13949            <p>B</p>ˇ
13950            <p>C</p>ˇ
13951        "#
13952        .unindent(),
13953    );
13954
13955    // Toggle comments for mixture of empty and non-empty selections, where
13956    // multiple selections occupy a given line.
13957    cx.set_state(
13958        &r#"
13959            <p>A«</p>
13960            <p>ˇ»B</p>ˇ
13961            <p>C«</p>
13962            <p>ˇ»D</p>ˇ
13963        "#
13964        .unindent(),
13965    );
13966
13967    cx.update_editor(|editor, window, cx| {
13968        editor.toggle_comments(&ToggleComments::default(), window, cx)
13969    });
13970    cx.assert_editor_state(
13971        &r#"
13972            <!-- <p>A«</p>
13973            <p>ˇ»B</p>ˇ -->
13974            <!-- <p>C«</p>
13975            <p>ˇ»D</p>ˇ -->
13976        "#
13977        .unindent(),
13978    );
13979    cx.update_editor(|editor, window, cx| {
13980        editor.toggle_comments(&ToggleComments::default(), window, cx)
13981    });
13982    cx.assert_editor_state(
13983        &r#"
13984            <p>A«</p>
13985            <p>ˇ»B</p>ˇ
13986            <p>C«</p>
13987            <p>ˇ»D</p>ˇ
13988        "#
13989        .unindent(),
13990    );
13991
13992    // Toggle comments when different languages are active for different
13993    // selections.
13994    cx.set_state(
13995        &r#"
13996            ˇ<script>
13997                ˇvar x = new Y();
13998            ˇ</script>
13999        "#
14000        .unindent(),
14001    );
14002    cx.executor().run_until_parked();
14003    cx.update_editor(|editor, window, cx| {
14004        editor.toggle_comments(&ToggleComments::default(), window, cx)
14005    });
14006    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14007    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14008    cx.assert_editor_state(
14009        &r#"
14010            <!-- ˇ<script> -->
14011                // ˇvar x = new Y();
14012            <!-- ˇ</script> -->
14013        "#
14014        .unindent(),
14015    );
14016}
14017
14018#[gpui::test]
14019fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14020    init_test(cx, |_| {});
14021
14022    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14023    let multibuffer = cx.new(|cx| {
14024        let mut multibuffer = MultiBuffer::new(ReadWrite);
14025        multibuffer.push_excerpts(
14026            buffer.clone(),
14027            [
14028                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14029                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14030            ],
14031            cx,
14032        );
14033        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14034        multibuffer
14035    });
14036
14037    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14038    editor.update_in(cx, |editor, window, cx| {
14039        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14041            s.select_ranges([
14042                Point::new(0, 0)..Point::new(0, 0),
14043                Point::new(1, 0)..Point::new(1, 0),
14044            ])
14045        });
14046
14047        editor.handle_input("X", window, cx);
14048        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14049        assert_eq!(
14050            editor.selections.ranges(cx),
14051            [
14052                Point::new(0, 1)..Point::new(0, 1),
14053                Point::new(1, 1)..Point::new(1, 1),
14054            ]
14055        );
14056
14057        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14059            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14060        });
14061        editor.backspace(&Default::default(), window, cx);
14062        assert_eq!(editor.text(cx), "Xa\nbbb");
14063        assert_eq!(
14064            editor.selections.ranges(cx),
14065            [Point::new(1, 0)..Point::new(1, 0)]
14066        );
14067
14068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14069            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14070        });
14071        editor.backspace(&Default::default(), window, cx);
14072        assert_eq!(editor.text(cx), "X\nbb");
14073        assert_eq!(
14074            editor.selections.ranges(cx),
14075            [Point::new(0, 1)..Point::new(0, 1)]
14076        );
14077    });
14078}
14079
14080#[gpui::test]
14081fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14082    init_test(cx, |_| {});
14083
14084    let markers = vec![('[', ']').into(), ('(', ')').into()];
14085    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14086        indoc! {"
14087            [aaaa
14088            (bbbb]
14089            cccc)",
14090        },
14091        markers.clone(),
14092    );
14093    let excerpt_ranges = markers.into_iter().map(|marker| {
14094        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14095        ExcerptRange::new(context.clone())
14096    });
14097    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14098    let multibuffer = cx.new(|cx| {
14099        let mut multibuffer = MultiBuffer::new(ReadWrite);
14100        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14101        multibuffer
14102    });
14103
14104    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14105    editor.update_in(cx, |editor, window, cx| {
14106        let (expected_text, selection_ranges) = marked_text_ranges(
14107            indoc! {"
14108                aaaa
14109                bˇbbb
14110                bˇbbˇb
14111                cccc"
14112            },
14113            true,
14114        );
14115        assert_eq!(editor.text(cx), expected_text);
14116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14117            s.select_ranges(selection_ranges)
14118        });
14119
14120        editor.handle_input("X", window, cx);
14121
14122        let (expected_text, expected_selections) = marked_text_ranges(
14123            indoc! {"
14124                aaaa
14125                bXˇbbXb
14126                bXˇbbXˇb
14127                cccc"
14128            },
14129            false,
14130        );
14131        assert_eq!(editor.text(cx), expected_text);
14132        assert_eq!(editor.selections.ranges(cx), expected_selections);
14133
14134        editor.newline(&Newline, window, cx);
14135        let (expected_text, expected_selections) = marked_text_ranges(
14136            indoc! {"
14137                aaaa
14138                bX
14139                ˇbbX
14140                b
14141                bX
14142                ˇbbX
14143                ˇb
14144                cccc"
14145            },
14146            false,
14147        );
14148        assert_eq!(editor.text(cx), expected_text);
14149        assert_eq!(editor.selections.ranges(cx), expected_selections);
14150    });
14151}
14152
14153#[gpui::test]
14154fn test_refresh_selections(cx: &mut TestAppContext) {
14155    init_test(cx, |_| {});
14156
14157    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14158    let mut excerpt1_id = None;
14159    let multibuffer = cx.new(|cx| {
14160        let mut multibuffer = MultiBuffer::new(ReadWrite);
14161        excerpt1_id = multibuffer
14162            .push_excerpts(
14163                buffer.clone(),
14164                [
14165                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14166                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14167                ],
14168                cx,
14169            )
14170            .into_iter()
14171            .next();
14172        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14173        multibuffer
14174    });
14175
14176    let editor = cx.add_window(|window, cx| {
14177        let mut editor = build_editor(multibuffer.clone(), window, cx);
14178        let snapshot = editor.snapshot(window, cx);
14179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14180            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14181        });
14182        editor.begin_selection(
14183            Point::new(2, 1).to_display_point(&snapshot),
14184            true,
14185            1,
14186            window,
14187            cx,
14188        );
14189        assert_eq!(
14190            editor.selections.ranges(cx),
14191            [
14192                Point::new(1, 3)..Point::new(1, 3),
14193                Point::new(2, 1)..Point::new(2, 1),
14194            ]
14195        );
14196        editor
14197    });
14198
14199    // Refreshing selections is a no-op when excerpts haven't changed.
14200    _ = editor.update(cx, |editor, window, cx| {
14201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14202        assert_eq!(
14203            editor.selections.ranges(cx),
14204            [
14205                Point::new(1, 3)..Point::new(1, 3),
14206                Point::new(2, 1)..Point::new(2, 1),
14207            ]
14208        );
14209    });
14210
14211    multibuffer.update(cx, |multibuffer, cx| {
14212        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14213    });
14214    _ = editor.update(cx, |editor, window, cx| {
14215        // Removing an excerpt causes the first selection to become degenerate.
14216        assert_eq!(
14217            editor.selections.ranges(cx),
14218            [
14219                Point::new(0, 0)..Point::new(0, 0),
14220                Point::new(0, 1)..Point::new(0, 1)
14221            ]
14222        );
14223
14224        // Refreshing selections will relocate the first selection to the original buffer
14225        // location.
14226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14227        assert_eq!(
14228            editor.selections.ranges(cx),
14229            [
14230                Point::new(0, 1)..Point::new(0, 1),
14231                Point::new(0, 3)..Point::new(0, 3)
14232            ]
14233        );
14234        assert!(editor.selections.pending_anchor().is_some());
14235    });
14236}
14237
14238#[gpui::test]
14239fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14240    init_test(cx, |_| {});
14241
14242    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14243    let mut excerpt1_id = None;
14244    let multibuffer = cx.new(|cx| {
14245        let mut multibuffer = MultiBuffer::new(ReadWrite);
14246        excerpt1_id = multibuffer
14247            .push_excerpts(
14248                buffer.clone(),
14249                [
14250                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14251                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14252                ],
14253                cx,
14254            )
14255            .into_iter()
14256            .next();
14257        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14258        multibuffer
14259    });
14260
14261    let editor = cx.add_window(|window, cx| {
14262        let mut editor = build_editor(multibuffer.clone(), window, cx);
14263        let snapshot = editor.snapshot(window, cx);
14264        editor.begin_selection(
14265            Point::new(1, 3).to_display_point(&snapshot),
14266            false,
14267            1,
14268            window,
14269            cx,
14270        );
14271        assert_eq!(
14272            editor.selections.ranges(cx),
14273            [Point::new(1, 3)..Point::new(1, 3)]
14274        );
14275        editor
14276    });
14277
14278    multibuffer.update(cx, |multibuffer, cx| {
14279        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14280    });
14281    _ = editor.update(cx, |editor, window, cx| {
14282        assert_eq!(
14283            editor.selections.ranges(cx),
14284            [Point::new(0, 0)..Point::new(0, 0)]
14285        );
14286
14287        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14289        assert_eq!(
14290            editor.selections.ranges(cx),
14291            [Point::new(0, 3)..Point::new(0, 3)]
14292        );
14293        assert!(editor.selections.pending_anchor().is_some());
14294    });
14295}
14296
14297#[gpui::test]
14298async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14299    init_test(cx, |_| {});
14300
14301    let language = Arc::new(
14302        Language::new(
14303            LanguageConfig {
14304                brackets: BracketPairConfig {
14305                    pairs: vec![
14306                        BracketPair {
14307                            start: "{".to_string(),
14308                            end: "}".to_string(),
14309                            close: true,
14310                            surround: true,
14311                            newline: true,
14312                        },
14313                        BracketPair {
14314                            start: "/* ".to_string(),
14315                            end: " */".to_string(),
14316                            close: true,
14317                            surround: true,
14318                            newline: true,
14319                        },
14320                    ],
14321                    ..Default::default()
14322                },
14323                ..Default::default()
14324            },
14325            Some(tree_sitter_rust::LANGUAGE.into()),
14326        )
14327        .with_indents_query("")
14328        .unwrap(),
14329    );
14330
14331    let text = concat!(
14332        "{   }\n",     //
14333        "  x\n",       //
14334        "  /*   */\n", //
14335        "x\n",         //
14336        "{{} }\n",     //
14337    );
14338
14339    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14340    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14341    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14342    editor
14343        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14344        .await;
14345
14346    editor.update_in(cx, |editor, window, cx| {
14347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14348            s.select_display_ranges([
14349                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14350                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14351                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14352            ])
14353        });
14354        editor.newline(&Newline, window, cx);
14355
14356        assert_eq!(
14357            editor.buffer().read(cx).read(cx).text(),
14358            concat!(
14359                "{ \n",    // Suppress rustfmt
14360                "\n",      //
14361                "}\n",     //
14362                "  x\n",   //
14363                "  /* \n", //
14364                "  \n",    //
14365                "  */\n",  //
14366                "x\n",     //
14367                "{{} \n",  //
14368                "}\n",     //
14369            )
14370        );
14371    });
14372}
14373
14374#[gpui::test]
14375fn test_highlighted_ranges(cx: &mut TestAppContext) {
14376    init_test(cx, |_| {});
14377
14378    let editor = cx.add_window(|window, cx| {
14379        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14380        build_editor(buffer.clone(), window, cx)
14381    });
14382
14383    _ = editor.update(cx, |editor, window, cx| {
14384        struct Type1;
14385        struct Type2;
14386
14387        let buffer = editor.buffer.read(cx).snapshot(cx);
14388
14389        let anchor_range =
14390            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14391
14392        editor.highlight_background::<Type1>(
14393            &[
14394                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14395                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14396                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14397                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14398            ],
14399            |_| Hsla::red(),
14400            cx,
14401        );
14402        editor.highlight_background::<Type2>(
14403            &[
14404                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14405                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14406                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14407                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14408            ],
14409            |_| Hsla::green(),
14410            cx,
14411        );
14412
14413        let snapshot = editor.snapshot(window, cx);
14414        let mut highlighted_ranges = editor.background_highlights_in_range(
14415            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14416            &snapshot,
14417            cx.theme(),
14418        );
14419        // Enforce a consistent ordering based on color without relying on the ordering of the
14420        // highlight's `TypeId` which is non-executor.
14421        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14422        assert_eq!(
14423            highlighted_ranges,
14424            &[
14425                (
14426                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14427                    Hsla::red(),
14428                ),
14429                (
14430                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14431                    Hsla::red(),
14432                ),
14433                (
14434                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14435                    Hsla::green(),
14436                ),
14437                (
14438                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14439                    Hsla::green(),
14440                ),
14441            ]
14442        );
14443        assert_eq!(
14444            editor.background_highlights_in_range(
14445                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14446                &snapshot,
14447                cx.theme(),
14448            ),
14449            &[(
14450                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14451                Hsla::red(),
14452            )]
14453        );
14454    });
14455}
14456
14457#[gpui::test]
14458async fn test_following(cx: &mut TestAppContext) {
14459    init_test(cx, |_| {});
14460
14461    let fs = FakeFs::new(cx.executor());
14462    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14463
14464    let buffer = project.update(cx, |project, cx| {
14465        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14466        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14467    });
14468    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14469    let follower = cx.update(|cx| {
14470        cx.open_window(
14471            WindowOptions {
14472                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14473                    gpui::Point::new(px(0.), px(0.)),
14474                    gpui::Point::new(px(10.), px(80.)),
14475                ))),
14476                ..Default::default()
14477            },
14478            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14479        )
14480        .unwrap()
14481    });
14482
14483    let is_still_following = Rc::new(RefCell::new(true));
14484    let follower_edit_event_count = Rc::new(RefCell::new(0));
14485    let pending_update = Rc::new(RefCell::new(None));
14486    let leader_entity = leader.root(cx).unwrap();
14487    let follower_entity = follower.root(cx).unwrap();
14488    _ = follower.update(cx, {
14489        let update = pending_update.clone();
14490        let is_still_following = is_still_following.clone();
14491        let follower_edit_event_count = follower_edit_event_count.clone();
14492        |_, window, cx| {
14493            cx.subscribe_in(
14494                &leader_entity,
14495                window,
14496                move |_, leader, event, window, cx| {
14497                    leader.read(cx).add_event_to_update_proto(
14498                        event,
14499                        &mut update.borrow_mut(),
14500                        window,
14501                        cx,
14502                    );
14503                },
14504            )
14505            .detach();
14506
14507            cx.subscribe_in(
14508                &follower_entity,
14509                window,
14510                move |_, _, event: &EditorEvent, _window, _cx| {
14511                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14512                        *is_still_following.borrow_mut() = false;
14513                    }
14514
14515                    if let EditorEvent::BufferEdited = event {
14516                        *follower_edit_event_count.borrow_mut() += 1;
14517                    }
14518                },
14519            )
14520            .detach();
14521        }
14522    });
14523
14524    // Update the selections only
14525    _ = leader.update(cx, |leader, window, cx| {
14526        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14527            s.select_ranges([1..1])
14528        });
14529    });
14530    follower
14531        .update(cx, |follower, window, cx| {
14532            follower.apply_update_proto(
14533                &project,
14534                pending_update.borrow_mut().take().unwrap(),
14535                window,
14536                cx,
14537            )
14538        })
14539        .unwrap()
14540        .await
14541        .unwrap();
14542    _ = follower.update(cx, |follower, _, cx| {
14543        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14544    });
14545    assert!(*is_still_following.borrow());
14546    assert_eq!(*follower_edit_event_count.borrow(), 0);
14547
14548    // Update the scroll position only
14549    _ = leader.update(cx, |leader, window, cx| {
14550        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14551    });
14552    follower
14553        .update(cx, |follower, window, cx| {
14554            follower.apply_update_proto(
14555                &project,
14556                pending_update.borrow_mut().take().unwrap(),
14557                window,
14558                cx,
14559            )
14560        })
14561        .unwrap()
14562        .await
14563        .unwrap();
14564    assert_eq!(
14565        follower
14566            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14567            .unwrap(),
14568        gpui::Point::new(1.5, 3.5)
14569    );
14570    assert!(*is_still_following.borrow());
14571    assert_eq!(*follower_edit_event_count.borrow(), 0);
14572
14573    // Update the selections and scroll position. The follower's scroll position is updated
14574    // via autoscroll, not via the leader's exact scroll position.
14575    _ = leader.update(cx, |leader, window, cx| {
14576        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14577            s.select_ranges([0..0])
14578        });
14579        leader.request_autoscroll(Autoscroll::newest(), cx);
14580        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14581    });
14582    follower
14583        .update(cx, |follower, window, cx| {
14584            follower.apply_update_proto(
14585                &project,
14586                pending_update.borrow_mut().take().unwrap(),
14587                window,
14588                cx,
14589            )
14590        })
14591        .unwrap()
14592        .await
14593        .unwrap();
14594    _ = follower.update(cx, |follower, _, cx| {
14595        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14596        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14597    });
14598    assert!(*is_still_following.borrow());
14599
14600    // Creating a pending selection that precedes another selection
14601    _ = leader.update(cx, |leader, window, cx| {
14602        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14603            s.select_ranges([1..1])
14604        });
14605        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14606    });
14607    follower
14608        .update(cx, |follower, window, cx| {
14609            follower.apply_update_proto(
14610                &project,
14611                pending_update.borrow_mut().take().unwrap(),
14612                window,
14613                cx,
14614            )
14615        })
14616        .unwrap()
14617        .await
14618        .unwrap();
14619    _ = follower.update(cx, |follower, _, cx| {
14620        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14621    });
14622    assert!(*is_still_following.borrow());
14623
14624    // Extend the pending selection so that it surrounds another selection
14625    _ = leader.update(cx, |leader, window, cx| {
14626        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14627    });
14628    follower
14629        .update(cx, |follower, window, cx| {
14630            follower.apply_update_proto(
14631                &project,
14632                pending_update.borrow_mut().take().unwrap(),
14633                window,
14634                cx,
14635            )
14636        })
14637        .unwrap()
14638        .await
14639        .unwrap();
14640    _ = follower.update(cx, |follower, _, cx| {
14641        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14642    });
14643
14644    // Scrolling locally breaks the follow
14645    _ = follower.update(cx, |follower, window, cx| {
14646        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14647        follower.set_scroll_anchor(
14648            ScrollAnchor {
14649                anchor: top_anchor,
14650                offset: gpui::Point::new(0.0, 0.5),
14651            },
14652            window,
14653            cx,
14654        );
14655    });
14656    assert!(!(*is_still_following.borrow()));
14657}
14658
14659#[gpui::test]
14660async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14661    init_test(cx, |_| {});
14662
14663    let fs = FakeFs::new(cx.executor());
14664    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14665    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14666    let pane = workspace
14667        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14668        .unwrap();
14669
14670    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14671
14672    let leader = pane.update_in(cx, |_, window, cx| {
14673        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14674        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14675    });
14676
14677    // Start following the editor when it has no excerpts.
14678    let mut state_message =
14679        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14680    let workspace_entity = workspace.root(cx).unwrap();
14681    let follower_1 = cx
14682        .update_window(*workspace.deref(), |_, window, cx| {
14683            Editor::from_state_proto(
14684                workspace_entity,
14685                ViewId {
14686                    creator: CollaboratorId::PeerId(PeerId::default()),
14687                    id: 0,
14688                },
14689                &mut state_message,
14690                window,
14691                cx,
14692            )
14693        })
14694        .unwrap()
14695        .unwrap()
14696        .await
14697        .unwrap();
14698
14699    let update_message = Rc::new(RefCell::new(None));
14700    follower_1.update_in(cx, {
14701        let update = update_message.clone();
14702        |_, window, cx| {
14703            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14704                leader.read(cx).add_event_to_update_proto(
14705                    event,
14706                    &mut update.borrow_mut(),
14707                    window,
14708                    cx,
14709                );
14710            })
14711            .detach();
14712        }
14713    });
14714
14715    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14716        (
14717            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14718            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14719        )
14720    });
14721
14722    // Insert some excerpts.
14723    leader.update(cx, |leader, cx| {
14724        leader.buffer.update(cx, |multibuffer, cx| {
14725            multibuffer.set_excerpts_for_path(
14726                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14727                buffer_1.clone(),
14728                vec![
14729                    Point::row_range(0..3),
14730                    Point::row_range(1..6),
14731                    Point::row_range(12..15),
14732                ],
14733                0,
14734                cx,
14735            );
14736            multibuffer.set_excerpts_for_path(
14737                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14738                buffer_2.clone(),
14739                vec![Point::row_range(0..6), Point::row_range(8..12)],
14740                0,
14741                cx,
14742            );
14743        });
14744    });
14745
14746    // Apply the update of adding the excerpts.
14747    follower_1
14748        .update_in(cx, |follower, window, cx| {
14749            follower.apply_update_proto(
14750                &project,
14751                update_message.borrow().clone().unwrap(),
14752                window,
14753                cx,
14754            )
14755        })
14756        .await
14757        .unwrap();
14758    assert_eq!(
14759        follower_1.update(cx, |editor, cx| editor.text(cx)),
14760        leader.update(cx, |editor, cx| editor.text(cx))
14761    );
14762    update_message.borrow_mut().take();
14763
14764    // Start following separately after it already has excerpts.
14765    let mut state_message =
14766        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14767    let workspace_entity = workspace.root(cx).unwrap();
14768    let follower_2 = cx
14769        .update_window(*workspace.deref(), |_, window, cx| {
14770            Editor::from_state_proto(
14771                workspace_entity,
14772                ViewId {
14773                    creator: CollaboratorId::PeerId(PeerId::default()),
14774                    id: 0,
14775                },
14776                &mut state_message,
14777                window,
14778                cx,
14779            )
14780        })
14781        .unwrap()
14782        .unwrap()
14783        .await
14784        .unwrap();
14785    assert_eq!(
14786        follower_2.update(cx, |editor, cx| editor.text(cx)),
14787        leader.update(cx, |editor, cx| editor.text(cx))
14788    );
14789
14790    // Remove some excerpts.
14791    leader.update(cx, |leader, cx| {
14792        leader.buffer.update(cx, |multibuffer, cx| {
14793            let excerpt_ids = multibuffer.excerpt_ids();
14794            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14795            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14796        });
14797    });
14798
14799    // Apply the update of removing the excerpts.
14800    follower_1
14801        .update_in(cx, |follower, window, cx| {
14802            follower.apply_update_proto(
14803                &project,
14804                update_message.borrow().clone().unwrap(),
14805                window,
14806                cx,
14807            )
14808        })
14809        .await
14810        .unwrap();
14811    follower_2
14812        .update_in(cx, |follower, window, cx| {
14813            follower.apply_update_proto(
14814                &project,
14815                update_message.borrow().clone().unwrap(),
14816                window,
14817                cx,
14818            )
14819        })
14820        .await
14821        .unwrap();
14822    update_message.borrow_mut().take();
14823    assert_eq!(
14824        follower_1.update(cx, |editor, cx| editor.text(cx)),
14825        leader.update(cx, |editor, cx| editor.text(cx))
14826    );
14827}
14828
14829#[gpui::test]
14830async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14831    init_test(cx, |_| {});
14832
14833    let mut cx = EditorTestContext::new(cx).await;
14834    let lsp_store =
14835        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14836
14837    cx.set_state(indoc! {"
14838        ˇfn func(abc def: i32) -> u32 {
14839        }
14840    "});
14841
14842    cx.update(|_, cx| {
14843        lsp_store.update(cx, |lsp_store, cx| {
14844            lsp_store
14845                .update_diagnostics(
14846                    LanguageServerId(0),
14847                    lsp::PublishDiagnosticsParams {
14848                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14849                        version: None,
14850                        diagnostics: vec![
14851                            lsp::Diagnostic {
14852                                range: lsp::Range::new(
14853                                    lsp::Position::new(0, 11),
14854                                    lsp::Position::new(0, 12),
14855                                ),
14856                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14857                                ..Default::default()
14858                            },
14859                            lsp::Diagnostic {
14860                                range: lsp::Range::new(
14861                                    lsp::Position::new(0, 12),
14862                                    lsp::Position::new(0, 15),
14863                                ),
14864                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14865                                ..Default::default()
14866                            },
14867                            lsp::Diagnostic {
14868                                range: lsp::Range::new(
14869                                    lsp::Position::new(0, 25),
14870                                    lsp::Position::new(0, 28),
14871                                ),
14872                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14873                                ..Default::default()
14874                            },
14875                        ],
14876                    },
14877                    None,
14878                    DiagnosticSourceKind::Pushed,
14879                    &[],
14880                    cx,
14881                )
14882                .unwrap()
14883        });
14884    });
14885
14886    executor.run_until_parked();
14887
14888    cx.update_editor(|editor, window, cx| {
14889        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14890    });
14891
14892    cx.assert_editor_state(indoc! {"
14893        fn func(abc def: i32) -> ˇu32 {
14894        }
14895    "});
14896
14897    cx.update_editor(|editor, window, cx| {
14898        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14899    });
14900
14901    cx.assert_editor_state(indoc! {"
14902        fn func(abc ˇdef: i32) -> u32 {
14903        }
14904    "});
14905
14906    cx.update_editor(|editor, window, cx| {
14907        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14908    });
14909
14910    cx.assert_editor_state(indoc! {"
14911        fn func(abcˇ def: i32) -> u32 {
14912        }
14913    "});
14914
14915    cx.update_editor(|editor, window, cx| {
14916        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14917    });
14918
14919    cx.assert_editor_state(indoc! {"
14920        fn func(abc def: i32) -> ˇu32 {
14921        }
14922    "});
14923}
14924
14925#[gpui::test]
14926async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14927    init_test(cx, |_| {});
14928
14929    let mut cx = EditorTestContext::new(cx).await;
14930
14931    let diff_base = r#"
14932        use some::mod;
14933
14934        const A: u32 = 42;
14935
14936        fn main() {
14937            println!("hello");
14938
14939            println!("world");
14940        }
14941        "#
14942    .unindent();
14943
14944    // Edits are modified, removed, modified, added
14945    cx.set_state(
14946        &r#"
14947        use some::modified;
14948
14949        ˇ
14950        fn main() {
14951            println!("hello there");
14952
14953            println!("around the");
14954            println!("world");
14955        }
14956        "#
14957        .unindent(),
14958    );
14959
14960    cx.set_head_text(&diff_base);
14961    executor.run_until_parked();
14962
14963    cx.update_editor(|editor, window, cx| {
14964        //Wrap around the bottom of the buffer
14965        for _ in 0..3 {
14966            editor.go_to_next_hunk(&GoToHunk, window, cx);
14967        }
14968    });
14969
14970    cx.assert_editor_state(
14971        &r#"
14972        ˇuse some::modified;
14973
14974
14975        fn main() {
14976            println!("hello there");
14977
14978            println!("around the");
14979            println!("world");
14980        }
14981        "#
14982        .unindent(),
14983    );
14984
14985    cx.update_editor(|editor, window, cx| {
14986        //Wrap around the top of the buffer
14987        for _ in 0..2 {
14988            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14989        }
14990    });
14991
14992    cx.assert_editor_state(
14993        &r#"
14994        use some::modified;
14995
14996
14997        fn main() {
14998        ˇ    println!("hello there");
14999
15000            println!("around the");
15001            println!("world");
15002        }
15003        "#
15004        .unindent(),
15005    );
15006
15007    cx.update_editor(|editor, window, cx| {
15008        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15009    });
15010
15011    cx.assert_editor_state(
15012        &r#"
15013        use some::modified;
15014
15015        ˇ
15016        fn main() {
15017            println!("hello there");
15018
15019            println!("around the");
15020            println!("world");
15021        }
15022        "#
15023        .unindent(),
15024    );
15025
15026    cx.update_editor(|editor, window, cx| {
15027        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15028    });
15029
15030    cx.assert_editor_state(
15031        &r#"
15032        ˇuse some::modified;
15033
15034
15035        fn main() {
15036            println!("hello there");
15037
15038            println!("around the");
15039            println!("world");
15040        }
15041        "#
15042        .unindent(),
15043    );
15044
15045    cx.update_editor(|editor, window, cx| {
15046        for _ in 0..2 {
15047            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15048        }
15049    });
15050
15051    cx.assert_editor_state(
15052        &r#"
15053        use some::modified;
15054
15055
15056        fn main() {
15057        ˇ    println!("hello there");
15058
15059            println!("around the");
15060            println!("world");
15061        }
15062        "#
15063        .unindent(),
15064    );
15065
15066    cx.update_editor(|editor, window, cx| {
15067        editor.fold(&Fold, window, cx);
15068    });
15069
15070    cx.update_editor(|editor, window, cx| {
15071        editor.go_to_next_hunk(&GoToHunk, window, cx);
15072    });
15073
15074    cx.assert_editor_state(
15075        &r#"
15076        ˇuse some::modified;
15077
15078
15079        fn main() {
15080            println!("hello there");
15081
15082            println!("around the");
15083            println!("world");
15084        }
15085        "#
15086        .unindent(),
15087    );
15088}
15089
15090#[test]
15091fn test_split_words() {
15092    fn split(text: &str) -> Vec<&str> {
15093        split_words(text).collect()
15094    }
15095
15096    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15097    assert_eq!(split("hello_world"), &["hello_", "world"]);
15098    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15099    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15100    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15101    assert_eq!(split("helloworld"), &["helloworld"]);
15102
15103    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15104}
15105
15106#[gpui::test]
15107async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15108    init_test(cx, |_| {});
15109
15110    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15111    let mut assert = |before, after| {
15112        let _state_context = cx.set_state(before);
15113        cx.run_until_parked();
15114        cx.update_editor(|editor, window, cx| {
15115            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15116        });
15117        cx.run_until_parked();
15118        cx.assert_editor_state(after);
15119    };
15120
15121    // Outside bracket jumps to outside of matching bracket
15122    assert("console.logˇ(var);", "console.log(var)ˇ;");
15123    assert("console.log(var)ˇ;", "console.logˇ(var);");
15124
15125    // Inside bracket jumps to inside of matching bracket
15126    assert("console.log(ˇvar);", "console.log(varˇ);");
15127    assert("console.log(varˇ);", "console.log(ˇvar);");
15128
15129    // When outside a bracket and inside, favor jumping to the inside bracket
15130    assert(
15131        "console.log('foo', [1, 2, 3]ˇ);",
15132        "console.log(ˇ'foo', [1, 2, 3]);",
15133    );
15134    assert(
15135        "console.log(ˇ'foo', [1, 2, 3]);",
15136        "console.log('foo', [1, 2, 3]ˇ);",
15137    );
15138
15139    // Bias forward if two options are equally likely
15140    assert(
15141        "let result = curried_fun()ˇ();",
15142        "let result = curried_fun()()ˇ;",
15143    );
15144
15145    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15146    assert(
15147        indoc! {"
15148            function test() {
15149                console.log('test')ˇ
15150            }"},
15151        indoc! {"
15152            function test() {
15153                console.logˇ('test')
15154            }"},
15155    );
15156}
15157
15158#[gpui::test]
15159async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15160    init_test(cx, |_| {});
15161
15162    let fs = FakeFs::new(cx.executor());
15163    fs.insert_tree(
15164        path!("/a"),
15165        json!({
15166            "main.rs": "fn main() { let a = 5; }",
15167            "other.rs": "// Test file",
15168        }),
15169    )
15170    .await;
15171    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15172
15173    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15174    language_registry.add(Arc::new(Language::new(
15175        LanguageConfig {
15176            name: "Rust".into(),
15177            matcher: LanguageMatcher {
15178                path_suffixes: vec!["rs".to_string()],
15179                ..Default::default()
15180            },
15181            brackets: BracketPairConfig {
15182                pairs: vec![BracketPair {
15183                    start: "{".to_string(),
15184                    end: "}".to_string(),
15185                    close: true,
15186                    surround: true,
15187                    newline: true,
15188                }],
15189                disabled_scopes_by_bracket_ix: Vec::new(),
15190            },
15191            ..Default::default()
15192        },
15193        Some(tree_sitter_rust::LANGUAGE.into()),
15194    )));
15195    let mut fake_servers = language_registry.register_fake_lsp(
15196        "Rust",
15197        FakeLspAdapter {
15198            capabilities: lsp::ServerCapabilities {
15199                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15200                    first_trigger_character: "{".to_string(),
15201                    more_trigger_character: None,
15202                }),
15203                ..Default::default()
15204            },
15205            ..Default::default()
15206        },
15207    );
15208
15209    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15210
15211    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15212
15213    let worktree_id = workspace
15214        .update(cx, |workspace, _, cx| {
15215            workspace.project().update(cx, |project, cx| {
15216                project.worktrees(cx).next().unwrap().read(cx).id()
15217            })
15218        })
15219        .unwrap();
15220
15221    let buffer = project
15222        .update(cx, |project, cx| {
15223            project.open_local_buffer(path!("/a/main.rs"), cx)
15224        })
15225        .await
15226        .unwrap();
15227    let editor_handle = workspace
15228        .update(cx, |workspace, window, cx| {
15229            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15230        })
15231        .unwrap()
15232        .await
15233        .unwrap()
15234        .downcast::<Editor>()
15235        .unwrap();
15236
15237    cx.executor().start_waiting();
15238    let fake_server = fake_servers.next().await.unwrap();
15239
15240    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15241        |params, _| async move {
15242            assert_eq!(
15243                params.text_document_position.text_document.uri,
15244                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15245            );
15246            assert_eq!(
15247                params.text_document_position.position,
15248                lsp::Position::new(0, 21),
15249            );
15250
15251            Ok(Some(vec![lsp::TextEdit {
15252                new_text: "]".to_string(),
15253                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15254            }]))
15255        },
15256    );
15257
15258    editor_handle.update_in(cx, |editor, window, cx| {
15259        window.focus(&editor.focus_handle(cx));
15260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15261            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15262        });
15263        editor.handle_input("{", window, cx);
15264    });
15265
15266    cx.executor().run_until_parked();
15267
15268    buffer.update(cx, |buffer, _| {
15269        assert_eq!(
15270            buffer.text(),
15271            "fn main() { let a = {5}; }",
15272            "No extra braces from on type formatting should appear in the buffer"
15273        )
15274    });
15275}
15276
15277#[gpui::test(iterations = 20, seeds(31))]
15278async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15279    init_test(cx, |_| {});
15280
15281    let mut cx = EditorLspTestContext::new_rust(
15282        lsp::ServerCapabilities {
15283            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15284                first_trigger_character: ".".to_string(),
15285                more_trigger_character: None,
15286            }),
15287            ..Default::default()
15288        },
15289        cx,
15290    )
15291    .await;
15292
15293    cx.update_buffer(|buffer, _| {
15294        // This causes autoindent to be async.
15295        buffer.set_sync_parse_timeout(Duration::ZERO)
15296    });
15297
15298    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15299    cx.simulate_keystroke("\n");
15300    cx.run_until_parked();
15301
15302    let buffer_cloned =
15303        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15304    let mut request =
15305        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15306            let buffer_cloned = buffer_cloned.clone();
15307            async move {
15308                buffer_cloned.update(&mut cx, |buffer, _| {
15309                    assert_eq!(
15310                        buffer.text(),
15311                        "fn c() {\n    d()\n        .\n}\n",
15312                        "OnTypeFormatting should triggered after autoindent applied"
15313                    )
15314                })?;
15315
15316                Ok(Some(vec![]))
15317            }
15318        });
15319
15320    cx.simulate_keystroke(".");
15321    cx.run_until_parked();
15322
15323    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15324    assert!(request.next().await.is_some());
15325    request.close();
15326    assert!(request.next().await.is_none());
15327}
15328
15329#[gpui::test]
15330async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15331    init_test(cx, |_| {});
15332
15333    let fs = FakeFs::new(cx.executor());
15334    fs.insert_tree(
15335        path!("/a"),
15336        json!({
15337            "main.rs": "fn main() { let a = 5; }",
15338            "other.rs": "// Test file",
15339        }),
15340    )
15341    .await;
15342
15343    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15344
15345    let server_restarts = Arc::new(AtomicUsize::new(0));
15346    let closure_restarts = Arc::clone(&server_restarts);
15347    let language_server_name = "test language server";
15348    let language_name: LanguageName = "Rust".into();
15349
15350    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15351    language_registry.add(Arc::new(Language::new(
15352        LanguageConfig {
15353            name: language_name.clone(),
15354            matcher: LanguageMatcher {
15355                path_suffixes: vec!["rs".to_string()],
15356                ..Default::default()
15357            },
15358            ..Default::default()
15359        },
15360        Some(tree_sitter_rust::LANGUAGE.into()),
15361    )));
15362    let mut fake_servers = language_registry.register_fake_lsp(
15363        "Rust",
15364        FakeLspAdapter {
15365            name: language_server_name,
15366            initialization_options: Some(json!({
15367                "testOptionValue": true
15368            })),
15369            initializer: Some(Box::new(move |fake_server| {
15370                let task_restarts = Arc::clone(&closure_restarts);
15371                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15372                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15373                    futures::future::ready(Ok(()))
15374                });
15375            })),
15376            ..Default::default()
15377        },
15378    );
15379
15380    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15381    let _buffer = project
15382        .update(cx, |project, cx| {
15383            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15384        })
15385        .await
15386        .unwrap();
15387    let _fake_server = fake_servers.next().await.unwrap();
15388    update_test_language_settings(cx, |language_settings| {
15389        language_settings.languages.0.insert(
15390            language_name.clone(),
15391            LanguageSettingsContent {
15392                tab_size: NonZeroU32::new(8),
15393                ..Default::default()
15394            },
15395        );
15396    });
15397    cx.executor().run_until_parked();
15398    assert_eq!(
15399        server_restarts.load(atomic::Ordering::Acquire),
15400        0,
15401        "Should not restart LSP server on an unrelated change"
15402    );
15403
15404    update_test_project_settings(cx, |project_settings| {
15405        project_settings.lsp.insert(
15406            "Some other server name".into(),
15407            LspSettings {
15408                binary: None,
15409                settings: None,
15410                initialization_options: Some(json!({
15411                    "some other init value": false
15412                })),
15413                enable_lsp_tasks: false,
15414            },
15415        );
15416    });
15417    cx.executor().run_until_parked();
15418    assert_eq!(
15419        server_restarts.load(atomic::Ordering::Acquire),
15420        0,
15421        "Should not restart LSP server on an unrelated LSP settings change"
15422    );
15423
15424    update_test_project_settings(cx, |project_settings| {
15425        project_settings.lsp.insert(
15426            language_server_name.into(),
15427            LspSettings {
15428                binary: None,
15429                settings: None,
15430                initialization_options: Some(json!({
15431                    "anotherInitValue": false
15432                })),
15433                enable_lsp_tasks: false,
15434            },
15435        );
15436    });
15437    cx.executor().run_until_parked();
15438    assert_eq!(
15439        server_restarts.load(atomic::Ordering::Acquire),
15440        1,
15441        "Should restart LSP server on a related LSP settings change"
15442    );
15443
15444    update_test_project_settings(cx, |project_settings| {
15445        project_settings.lsp.insert(
15446            language_server_name.into(),
15447            LspSettings {
15448                binary: None,
15449                settings: None,
15450                initialization_options: Some(json!({
15451                    "anotherInitValue": false
15452                })),
15453                enable_lsp_tasks: false,
15454            },
15455        );
15456    });
15457    cx.executor().run_until_parked();
15458    assert_eq!(
15459        server_restarts.load(atomic::Ordering::Acquire),
15460        1,
15461        "Should not restart LSP server on a related LSP settings change that is the same"
15462    );
15463
15464    update_test_project_settings(cx, |project_settings| {
15465        project_settings.lsp.insert(
15466            language_server_name.into(),
15467            LspSettings {
15468                binary: None,
15469                settings: None,
15470                initialization_options: None,
15471                enable_lsp_tasks: false,
15472            },
15473        );
15474    });
15475    cx.executor().run_until_parked();
15476    assert_eq!(
15477        server_restarts.load(atomic::Ordering::Acquire),
15478        2,
15479        "Should restart LSP server on another related LSP settings change"
15480    );
15481}
15482
15483#[gpui::test]
15484async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15485    init_test(cx, |_| {});
15486
15487    let mut cx = EditorLspTestContext::new_rust(
15488        lsp::ServerCapabilities {
15489            completion_provider: Some(lsp::CompletionOptions {
15490                trigger_characters: Some(vec![".".to_string()]),
15491                resolve_provider: Some(true),
15492                ..Default::default()
15493            }),
15494            ..Default::default()
15495        },
15496        cx,
15497    )
15498    .await;
15499
15500    cx.set_state("fn main() { let a = 2ˇ; }");
15501    cx.simulate_keystroke(".");
15502    let completion_item = lsp::CompletionItem {
15503        label: "some".into(),
15504        kind: Some(lsp::CompletionItemKind::SNIPPET),
15505        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15506        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15507            kind: lsp::MarkupKind::Markdown,
15508            value: "```rust\nSome(2)\n```".to_string(),
15509        })),
15510        deprecated: Some(false),
15511        sort_text: Some("fffffff2".to_string()),
15512        filter_text: Some("some".to_string()),
15513        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15514        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15515            range: lsp::Range {
15516                start: lsp::Position {
15517                    line: 0,
15518                    character: 22,
15519                },
15520                end: lsp::Position {
15521                    line: 0,
15522                    character: 22,
15523                },
15524            },
15525            new_text: "Some(2)".to_string(),
15526        })),
15527        additional_text_edits: Some(vec![lsp::TextEdit {
15528            range: lsp::Range {
15529                start: lsp::Position {
15530                    line: 0,
15531                    character: 20,
15532                },
15533                end: lsp::Position {
15534                    line: 0,
15535                    character: 22,
15536                },
15537            },
15538            new_text: "".to_string(),
15539        }]),
15540        ..Default::default()
15541    };
15542
15543    let closure_completion_item = completion_item.clone();
15544    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15545        let task_completion_item = closure_completion_item.clone();
15546        async move {
15547            Ok(Some(lsp::CompletionResponse::Array(vec![
15548                task_completion_item,
15549            ])))
15550        }
15551    });
15552
15553    request.next().await;
15554
15555    cx.condition(|editor, _| editor.context_menu_visible())
15556        .await;
15557    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15558        editor
15559            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15560            .unwrap()
15561    });
15562    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15563
15564    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15565        let task_completion_item = completion_item.clone();
15566        async move { Ok(task_completion_item) }
15567    })
15568    .next()
15569    .await
15570    .unwrap();
15571    apply_additional_edits.await.unwrap();
15572    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15573}
15574
15575#[gpui::test]
15576async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15577    init_test(cx, |_| {});
15578
15579    let mut cx = EditorLspTestContext::new_rust(
15580        lsp::ServerCapabilities {
15581            completion_provider: Some(lsp::CompletionOptions {
15582                trigger_characters: Some(vec![".".to_string()]),
15583                resolve_provider: Some(true),
15584                ..Default::default()
15585            }),
15586            ..Default::default()
15587        },
15588        cx,
15589    )
15590    .await;
15591
15592    cx.set_state("fn main() { let a = 2ˇ; }");
15593    cx.simulate_keystroke(".");
15594
15595    let item1 = lsp::CompletionItem {
15596        label: "method id()".to_string(),
15597        filter_text: Some("id".to_string()),
15598        detail: None,
15599        documentation: None,
15600        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15601            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15602            new_text: ".id".to_string(),
15603        })),
15604        ..lsp::CompletionItem::default()
15605    };
15606
15607    let item2 = lsp::CompletionItem {
15608        label: "other".to_string(),
15609        filter_text: Some("other".to_string()),
15610        detail: None,
15611        documentation: None,
15612        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15613            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15614            new_text: ".other".to_string(),
15615        })),
15616        ..lsp::CompletionItem::default()
15617    };
15618
15619    let item1 = item1.clone();
15620    cx.set_request_handler::<lsp::request::Completion, _, _>({
15621        let item1 = item1.clone();
15622        move |_, _, _| {
15623            let item1 = item1.clone();
15624            let item2 = item2.clone();
15625            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15626        }
15627    })
15628    .next()
15629    .await;
15630
15631    cx.condition(|editor, _| editor.context_menu_visible())
15632        .await;
15633    cx.update_editor(|editor, _, _| {
15634        let context_menu = editor.context_menu.borrow_mut();
15635        let context_menu = context_menu
15636            .as_ref()
15637            .expect("Should have the context menu deployed");
15638        match context_menu {
15639            CodeContextMenu::Completions(completions_menu) => {
15640                let completions = completions_menu.completions.borrow_mut();
15641                assert_eq!(
15642                    completions
15643                        .iter()
15644                        .map(|completion| &completion.label.text)
15645                        .collect::<Vec<_>>(),
15646                    vec!["method id()", "other"]
15647                )
15648            }
15649            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15650        }
15651    });
15652
15653    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15654        let item1 = item1.clone();
15655        move |_, item_to_resolve, _| {
15656            let item1 = item1.clone();
15657            async move {
15658                if item1 == item_to_resolve {
15659                    Ok(lsp::CompletionItem {
15660                        label: "method id()".to_string(),
15661                        filter_text: Some("id".to_string()),
15662                        detail: Some("Now resolved!".to_string()),
15663                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15664                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15665                            range: lsp::Range::new(
15666                                lsp::Position::new(0, 22),
15667                                lsp::Position::new(0, 22),
15668                            ),
15669                            new_text: ".id".to_string(),
15670                        })),
15671                        ..lsp::CompletionItem::default()
15672                    })
15673                } else {
15674                    Ok(item_to_resolve)
15675                }
15676            }
15677        }
15678    })
15679    .next()
15680    .await
15681    .unwrap();
15682    cx.run_until_parked();
15683
15684    cx.update_editor(|editor, window, cx| {
15685        editor.context_menu_next(&Default::default(), window, cx);
15686    });
15687
15688    cx.update_editor(|editor, _, _| {
15689        let context_menu = editor.context_menu.borrow_mut();
15690        let context_menu = context_menu
15691            .as_ref()
15692            .expect("Should have the context menu deployed");
15693        match context_menu {
15694            CodeContextMenu::Completions(completions_menu) => {
15695                let completions = completions_menu.completions.borrow_mut();
15696                assert_eq!(
15697                    completions
15698                        .iter()
15699                        .map(|completion| &completion.label.text)
15700                        .collect::<Vec<_>>(),
15701                    vec!["method id() Now resolved!", "other"],
15702                    "Should update first completion label, but not second as the filter text did not match."
15703                );
15704            }
15705            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15706        }
15707    });
15708}
15709
15710#[gpui::test]
15711async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15712    init_test(cx, |_| {});
15713    let mut cx = EditorLspTestContext::new_rust(
15714        lsp::ServerCapabilities {
15715            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15716            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15717            completion_provider: Some(lsp::CompletionOptions {
15718                resolve_provider: Some(true),
15719                ..Default::default()
15720            }),
15721            ..Default::default()
15722        },
15723        cx,
15724    )
15725    .await;
15726    cx.set_state(indoc! {"
15727        struct TestStruct {
15728            field: i32
15729        }
15730
15731        fn mainˇ() {
15732            let unused_var = 42;
15733            let test_struct = TestStruct { field: 42 };
15734        }
15735    "});
15736    let symbol_range = cx.lsp_range(indoc! {"
15737        struct TestStruct {
15738            field: i32
15739        }
15740
15741        «fn main»() {
15742            let unused_var = 42;
15743            let test_struct = TestStruct { field: 42 };
15744        }
15745    "});
15746    let mut hover_requests =
15747        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15748            Ok(Some(lsp::Hover {
15749                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15750                    kind: lsp::MarkupKind::Markdown,
15751                    value: "Function documentation".to_string(),
15752                }),
15753                range: Some(symbol_range),
15754            }))
15755        });
15756
15757    // Case 1: Test that code action menu hide hover popover
15758    cx.dispatch_action(Hover);
15759    hover_requests.next().await;
15760    cx.condition(|editor, _| editor.hover_state.visible()).await;
15761    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15762        move |_, _, _| async move {
15763            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15764                lsp::CodeAction {
15765                    title: "Remove unused variable".to_string(),
15766                    kind: Some(CodeActionKind::QUICKFIX),
15767                    edit: Some(lsp::WorkspaceEdit {
15768                        changes: Some(
15769                            [(
15770                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15771                                vec![lsp::TextEdit {
15772                                    range: lsp::Range::new(
15773                                        lsp::Position::new(5, 4),
15774                                        lsp::Position::new(5, 27),
15775                                    ),
15776                                    new_text: "".to_string(),
15777                                }],
15778                            )]
15779                            .into_iter()
15780                            .collect(),
15781                        ),
15782                        ..Default::default()
15783                    }),
15784                    ..Default::default()
15785                },
15786            )]))
15787        },
15788    );
15789    cx.update_editor(|editor, window, cx| {
15790        editor.toggle_code_actions(
15791            &ToggleCodeActions {
15792                deployed_from: None,
15793                quick_launch: false,
15794            },
15795            window,
15796            cx,
15797        );
15798    });
15799    code_action_requests.next().await;
15800    cx.run_until_parked();
15801    cx.condition(|editor, _| editor.context_menu_visible())
15802        .await;
15803    cx.update_editor(|editor, _, _| {
15804        assert!(
15805            !editor.hover_state.visible(),
15806            "Hover popover should be hidden when code action menu is shown"
15807        );
15808        // Hide code actions
15809        editor.context_menu.take();
15810    });
15811
15812    // Case 2: Test that code completions hide hover popover
15813    cx.dispatch_action(Hover);
15814    hover_requests.next().await;
15815    cx.condition(|editor, _| editor.hover_state.visible()).await;
15816    let counter = Arc::new(AtomicUsize::new(0));
15817    let mut completion_requests =
15818        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15819            let counter = counter.clone();
15820            async move {
15821                counter.fetch_add(1, atomic::Ordering::Release);
15822                Ok(Some(lsp::CompletionResponse::Array(vec![
15823                    lsp::CompletionItem {
15824                        label: "main".into(),
15825                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15826                        detail: Some("() -> ()".to_string()),
15827                        ..Default::default()
15828                    },
15829                    lsp::CompletionItem {
15830                        label: "TestStruct".into(),
15831                        kind: Some(lsp::CompletionItemKind::STRUCT),
15832                        detail: Some("struct TestStruct".to_string()),
15833                        ..Default::default()
15834                    },
15835                ])))
15836            }
15837        });
15838    cx.update_editor(|editor, window, cx| {
15839        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15840    });
15841    completion_requests.next().await;
15842    cx.condition(|editor, _| editor.context_menu_visible())
15843        .await;
15844    cx.update_editor(|editor, _, _| {
15845        assert!(
15846            !editor.hover_state.visible(),
15847            "Hover popover should be hidden when completion menu is shown"
15848        );
15849    });
15850}
15851
15852#[gpui::test]
15853async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15854    init_test(cx, |_| {});
15855
15856    let mut cx = EditorLspTestContext::new_rust(
15857        lsp::ServerCapabilities {
15858            completion_provider: Some(lsp::CompletionOptions {
15859                trigger_characters: Some(vec![".".to_string()]),
15860                resolve_provider: Some(true),
15861                ..Default::default()
15862            }),
15863            ..Default::default()
15864        },
15865        cx,
15866    )
15867    .await;
15868
15869    cx.set_state("fn main() { let a = 2ˇ; }");
15870    cx.simulate_keystroke(".");
15871
15872    let unresolved_item_1 = lsp::CompletionItem {
15873        label: "id".to_string(),
15874        filter_text: Some("id".to_string()),
15875        detail: None,
15876        documentation: None,
15877        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15878            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15879            new_text: ".id".to_string(),
15880        })),
15881        ..lsp::CompletionItem::default()
15882    };
15883    let resolved_item_1 = lsp::CompletionItem {
15884        additional_text_edits: Some(vec![lsp::TextEdit {
15885            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15886            new_text: "!!".to_string(),
15887        }]),
15888        ..unresolved_item_1.clone()
15889    };
15890    let unresolved_item_2 = lsp::CompletionItem {
15891        label: "other".to_string(),
15892        filter_text: Some("other".to_string()),
15893        detail: None,
15894        documentation: None,
15895        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15896            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15897            new_text: ".other".to_string(),
15898        })),
15899        ..lsp::CompletionItem::default()
15900    };
15901    let resolved_item_2 = lsp::CompletionItem {
15902        additional_text_edits: Some(vec![lsp::TextEdit {
15903            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15904            new_text: "??".to_string(),
15905        }]),
15906        ..unresolved_item_2.clone()
15907    };
15908
15909    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15910    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15911    cx.lsp
15912        .server
15913        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15914            let unresolved_item_1 = unresolved_item_1.clone();
15915            let resolved_item_1 = resolved_item_1.clone();
15916            let unresolved_item_2 = unresolved_item_2.clone();
15917            let resolved_item_2 = resolved_item_2.clone();
15918            let resolve_requests_1 = resolve_requests_1.clone();
15919            let resolve_requests_2 = resolve_requests_2.clone();
15920            move |unresolved_request, _| {
15921                let unresolved_item_1 = unresolved_item_1.clone();
15922                let resolved_item_1 = resolved_item_1.clone();
15923                let unresolved_item_2 = unresolved_item_2.clone();
15924                let resolved_item_2 = resolved_item_2.clone();
15925                let resolve_requests_1 = resolve_requests_1.clone();
15926                let resolve_requests_2 = resolve_requests_2.clone();
15927                async move {
15928                    if unresolved_request == unresolved_item_1 {
15929                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15930                        Ok(resolved_item_1.clone())
15931                    } else if unresolved_request == unresolved_item_2 {
15932                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15933                        Ok(resolved_item_2.clone())
15934                    } else {
15935                        panic!("Unexpected completion item {unresolved_request:?}")
15936                    }
15937                }
15938            }
15939        })
15940        .detach();
15941
15942    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15943        let unresolved_item_1 = unresolved_item_1.clone();
15944        let unresolved_item_2 = unresolved_item_2.clone();
15945        async move {
15946            Ok(Some(lsp::CompletionResponse::Array(vec![
15947                unresolved_item_1,
15948                unresolved_item_2,
15949            ])))
15950        }
15951    })
15952    .next()
15953    .await;
15954
15955    cx.condition(|editor, _| editor.context_menu_visible())
15956        .await;
15957    cx.update_editor(|editor, _, _| {
15958        let context_menu = editor.context_menu.borrow_mut();
15959        let context_menu = context_menu
15960            .as_ref()
15961            .expect("Should have the context menu deployed");
15962        match context_menu {
15963            CodeContextMenu::Completions(completions_menu) => {
15964                let completions = completions_menu.completions.borrow_mut();
15965                assert_eq!(
15966                    completions
15967                        .iter()
15968                        .map(|completion| &completion.label.text)
15969                        .collect::<Vec<_>>(),
15970                    vec!["id", "other"]
15971                )
15972            }
15973            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15974        }
15975    });
15976    cx.run_until_parked();
15977
15978    cx.update_editor(|editor, window, cx| {
15979        editor.context_menu_next(&ContextMenuNext, window, cx);
15980    });
15981    cx.run_until_parked();
15982    cx.update_editor(|editor, window, cx| {
15983        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15984    });
15985    cx.run_until_parked();
15986    cx.update_editor(|editor, window, cx| {
15987        editor.context_menu_next(&ContextMenuNext, window, cx);
15988    });
15989    cx.run_until_parked();
15990    cx.update_editor(|editor, window, cx| {
15991        editor
15992            .compose_completion(&ComposeCompletion::default(), window, cx)
15993            .expect("No task returned")
15994    })
15995    .await
15996    .expect("Completion failed");
15997    cx.run_until_parked();
15998
15999    cx.update_editor(|editor, _, cx| {
16000        assert_eq!(
16001            resolve_requests_1.load(atomic::Ordering::Acquire),
16002            1,
16003            "Should always resolve once despite multiple selections"
16004        );
16005        assert_eq!(
16006            resolve_requests_2.load(atomic::Ordering::Acquire),
16007            1,
16008            "Should always resolve once after multiple selections and applying the completion"
16009        );
16010        assert_eq!(
16011            editor.text(cx),
16012            "fn main() { let a = ??.other; }",
16013            "Should use resolved data when applying the completion"
16014        );
16015    });
16016}
16017
16018#[gpui::test]
16019async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16020    init_test(cx, |_| {});
16021
16022    let item_0 = lsp::CompletionItem {
16023        label: "abs".into(),
16024        insert_text: Some("abs".into()),
16025        data: Some(json!({ "very": "special"})),
16026        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16027        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16028            lsp::InsertReplaceEdit {
16029                new_text: "abs".to_string(),
16030                insert: lsp::Range::default(),
16031                replace: lsp::Range::default(),
16032            },
16033        )),
16034        ..lsp::CompletionItem::default()
16035    };
16036    let items = iter::once(item_0.clone())
16037        .chain((11..51).map(|i| lsp::CompletionItem {
16038            label: format!("item_{}", i),
16039            insert_text: Some(format!("item_{}", i)),
16040            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16041            ..lsp::CompletionItem::default()
16042        }))
16043        .collect::<Vec<_>>();
16044
16045    let default_commit_characters = vec!["?".to_string()];
16046    let default_data = json!({ "default": "data"});
16047    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16048    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16049    let default_edit_range = lsp::Range {
16050        start: lsp::Position {
16051            line: 0,
16052            character: 5,
16053        },
16054        end: lsp::Position {
16055            line: 0,
16056            character: 5,
16057        },
16058    };
16059
16060    let mut cx = EditorLspTestContext::new_rust(
16061        lsp::ServerCapabilities {
16062            completion_provider: Some(lsp::CompletionOptions {
16063                trigger_characters: Some(vec![".".to_string()]),
16064                resolve_provider: Some(true),
16065                ..Default::default()
16066            }),
16067            ..Default::default()
16068        },
16069        cx,
16070    )
16071    .await;
16072
16073    cx.set_state("fn main() { let a = 2ˇ; }");
16074    cx.simulate_keystroke(".");
16075
16076    let completion_data = default_data.clone();
16077    let completion_characters = default_commit_characters.clone();
16078    let completion_items = items.clone();
16079    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16080        let default_data = completion_data.clone();
16081        let default_commit_characters = completion_characters.clone();
16082        let items = completion_items.clone();
16083        async move {
16084            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16085                items,
16086                item_defaults: Some(lsp::CompletionListItemDefaults {
16087                    data: Some(default_data.clone()),
16088                    commit_characters: Some(default_commit_characters.clone()),
16089                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16090                        default_edit_range,
16091                    )),
16092                    insert_text_format: Some(default_insert_text_format),
16093                    insert_text_mode: Some(default_insert_text_mode),
16094                }),
16095                ..lsp::CompletionList::default()
16096            })))
16097        }
16098    })
16099    .next()
16100    .await;
16101
16102    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16103    cx.lsp
16104        .server
16105        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16106            let closure_resolved_items = resolved_items.clone();
16107            move |item_to_resolve, _| {
16108                let closure_resolved_items = closure_resolved_items.clone();
16109                async move {
16110                    closure_resolved_items.lock().push(item_to_resolve.clone());
16111                    Ok(item_to_resolve)
16112                }
16113            }
16114        })
16115        .detach();
16116
16117    cx.condition(|editor, _| editor.context_menu_visible())
16118        .await;
16119    cx.run_until_parked();
16120    cx.update_editor(|editor, _, _| {
16121        let menu = editor.context_menu.borrow_mut();
16122        match menu.as_ref().expect("should have the completions menu") {
16123            CodeContextMenu::Completions(completions_menu) => {
16124                assert_eq!(
16125                    completions_menu
16126                        .entries
16127                        .borrow()
16128                        .iter()
16129                        .map(|mat| mat.string.clone())
16130                        .collect::<Vec<String>>(),
16131                    items
16132                        .iter()
16133                        .map(|completion| completion.label.clone())
16134                        .collect::<Vec<String>>()
16135                );
16136            }
16137            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16138        }
16139    });
16140    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16141    // with 4 from the end.
16142    assert_eq!(
16143        *resolved_items.lock(),
16144        [&items[0..16], &items[items.len() - 4..items.len()]]
16145            .concat()
16146            .iter()
16147            .cloned()
16148            .map(|mut item| {
16149                if item.data.is_none() {
16150                    item.data = Some(default_data.clone());
16151                }
16152                item
16153            })
16154            .collect::<Vec<lsp::CompletionItem>>(),
16155        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16156    );
16157    resolved_items.lock().clear();
16158
16159    cx.update_editor(|editor, window, cx| {
16160        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16161    });
16162    cx.run_until_parked();
16163    // Completions that have already been resolved are skipped.
16164    assert_eq!(
16165        *resolved_items.lock(),
16166        items[items.len() - 17..items.len() - 4]
16167            .iter()
16168            .cloned()
16169            .map(|mut item| {
16170                if item.data.is_none() {
16171                    item.data = Some(default_data.clone());
16172                }
16173                item
16174            })
16175            .collect::<Vec<lsp::CompletionItem>>()
16176    );
16177    resolved_items.lock().clear();
16178}
16179
16180#[gpui::test]
16181async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16182    init_test(cx, |_| {});
16183
16184    let mut cx = EditorLspTestContext::new(
16185        Language::new(
16186            LanguageConfig {
16187                matcher: LanguageMatcher {
16188                    path_suffixes: vec!["jsx".into()],
16189                    ..Default::default()
16190                },
16191                overrides: [(
16192                    "element".into(),
16193                    LanguageConfigOverride {
16194                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16195                        ..Default::default()
16196                    },
16197                )]
16198                .into_iter()
16199                .collect(),
16200                ..Default::default()
16201            },
16202            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16203        )
16204        .with_override_query("(jsx_self_closing_element) @element")
16205        .unwrap(),
16206        lsp::ServerCapabilities {
16207            completion_provider: Some(lsp::CompletionOptions {
16208                trigger_characters: Some(vec![":".to_string()]),
16209                ..Default::default()
16210            }),
16211            ..Default::default()
16212        },
16213        cx,
16214    )
16215    .await;
16216
16217    cx.lsp
16218        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16219            Ok(Some(lsp::CompletionResponse::Array(vec![
16220                lsp::CompletionItem {
16221                    label: "bg-blue".into(),
16222                    ..Default::default()
16223                },
16224                lsp::CompletionItem {
16225                    label: "bg-red".into(),
16226                    ..Default::default()
16227                },
16228                lsp::CompletionItem {
16229                    label: "bg-yellow".into(),
16230                    ..Default::default()
16231                },
16232            ])))
16233        });
16234
16235    cx.set_state(r#"<p class="bgˇ" />"#);
16236
16237    // Trigger completion when typing a dash, because the dash is an extra
16238    // word character in the 'element' scope, which contains the cursor.
16239    cx.simulate_keystroke("-");
16240    cx.executor().run_until_parked();
16241    cx.update_editor(|editor, _, _| {
16242        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16243        {
16244            assert_eq!(
16245                completion_menu_entries(&menu),
16246                &["bg-blue", "bg-red", "bg-yellow"]
16247            );
16248        } else {
16249            panic!("expected completion menu to be open");
16250        }
16251    });
16252
16253    cx.simulate_keystroke("l");
16254    cx.executor().run_until_parked();
16255    cx.update_editor(|editor, _, _| {
16256        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16257        {
16258            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16259        } else {
16260            panic!("expected completion menu to be open");
16261        }
16262    });
16263
16264    // When filtering completions, consider the character after the '-' to
16265    // be the start of a subword.
16266    cx.set_state(r#"<p class="yelˇ" />"#);
16267    cx.simulate_keystroke("l");
16268    cx.executor().run_until_parked();
16269    cx.update_editor(|editor, _, _| {
16270        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16271        {
16272            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16273        } else {
16274            panic!("expected completion menu to be open");
16275        }
16276    });
16277}
16278
16279fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16280    let entries = menu.entries.borrow();
16281    entries.iter().map(|mat| mat.string.clone()).collect()
16282}
16283
16284#[gpui::test]
16285async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16286    init_test(cx, |settings| {
16287        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16288            Formatter::Prettier,
16289        )))
16290    });
16291
16292    let fs = FakeFs::new(cx.executor());
16293    fs.insert_file(path!("/file.ts"), Default::default()).await;
16294
16295    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16296    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16297
16298    language_registry.add(Arc::new(Language::new(
16299        LanguageConfig {
16300            name: "TypeScript".into(),
16301            matcher: LanguageMatcher {
16302                path_suffixes: vec!["ts".to_string()],
16303                ..Default::default()
16304            },
16305            ..Default::default()
16306        },
16307        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16308    )));
16309    update_test_language_settings(cx, |settings| {
16310        settings.defaults.prettier = Some(PrettierSettings {
16311            allowed: true,
16312            ..PrettierSettings::default()
16313        });
16314    });
16315
16316    let test_plugin = "test_plugin";
16317    let _ = language_registry.register_fake_lsp(
16318        "TypeScript",
16319        FakeLspAdapter {
16320            prettier_plugins: vec![test_plugin],
16321            ..Default::default()
16322        },
16323    );
16324
16325    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16326    let buffer = project
16327        .update(cx, |project, cx| {
16328            project.open_local_buffer(path!("/file.ts"), cx)
16329        })
16330        .await
16331        .unwrap();
16332
16333    let buffer_text = "one\ntwo\nthree\n";
16334    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16335    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16336    editor.update_in(cx, |editor, window, cx| {
16337        editor.set_text(buffer_text, window, cx)
16338    });
16339
16340    editor
16341        .update_in(cx, |editor, window, cx| {
16342            editor.perform_format(
16343                project.clone(),
16344                FormatTrigger::Manual,
16345                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16346                window,
16347                cx,
16348            )
16349        })
16350        .unwrap()
16351        .await;
16352    assert_eq!(
16353        editor.update(cx, |editor, cx| editor.text(cx)),
16354        buffer_text.to_string() + prettier_format_suffix,
16355        "Test prettier formatting was not applied to the original buffer text",
16356    );
16357
16358    update_test_language_settings(cx, |settings| {
16359        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16360    });
16361    let format = editor.update_in(cx, |editor, window, cx| {
16362        editor.perform_format(
16363            project.clone(),
16364            FormatTrigger::Manual,
16365            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16366            window,
16367            cx,
16368        )
16369    });
16370    format.await.unwrap();
16371    assert_eq!(
16372        editor.update(cx, |editor, cx| editor.text(cx)),
16373        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16374        "Autoformatting (via test prettier) was not applied to the original buffer text",
16375    );
16376}
16377
16378#[gpui::test]
16379async fn test_addition_reverts(cx: &mut TestAppContext) {
16380    init_test(cx, |_| {});
16381    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16382    let base_text = indoc! {r#"
16383        struct Row;
16384        struct Row1;
16385        struct Row2;
16386
16387        struct Row4;
16388        struct Row5;
16389        struct Row6;
16390
16391        struct Row8;
16392        struct Row9;
16393        struct Row10;"#};
16394
16395    // When addition hunks are not adjacent to carets, no hunk revert is performed
16396    assert_hunk_revert(
16397        indoc! {r#"struct Row;
16398                   struct Row1;
16399                   struct Row1.1;
16400                   struct Row1.2;
16401                   struct Row2;ˇ
16402
16403                   struct Row4;
16404                   struct Row5;
16405                   struct Row6;
16406
16407                   struct Row8;
16408                   ˇstruct Row9;
16409                   struct Row9.1;
16410                   struct Row9.2;
16411                   struct Row9.3;
16412                   struct Row10;"#},
16413        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16414        indoc! {r#"struct Row;
16415                   struct Row1;
16416                   struct Row1.1;
16417                   struct Row1.2;
16418                   struct Row2;ˇ
16419
16420                   struct Row4;
16421                   struct Row5;
16422                   struct Row6;
16423
16424                   struct Row8;
16425                   ˇstruct Row9;
16426                   struct Row9.1;
16427                   struct Row9.2;
16428                   struct Row9.3;
16429                   struct Row10;"#},
16430        base_text,
16431        &mut cx,
16432    );
16433    // Same for selections
16434    assert_hunk_revert(
16435        indoc! {r#"struct Row;
16436                   struct Row1;
16437                   struct Row2;
16438                   struct Row2.1;
16439                   struct Row2.2;
16440                   «ˇ
16441                   struct Row4;
16442                   struct» Row5;
16443                   «struct Row6;
16444                   ˇ»
16445                   struct Row9.1;
16446                   struct Row9.2;
16447                   struct Row9.3;
16448                   struct Row8;
16449                   struct Row9;
16450                   struct Row10;"#},
16451        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16452        indoc! {r#"struct Row;
16453                   struct Row1;
16454                   struct Row2;
16455                   struct Row2.1;
16456                   struct Row2.2;
16457                   «ˇ
16458                   struct Row4;
16459                   struct» Row5;
16460                   «struct Row6;
16461                   ˇ»
16462                   struct Row9.1;
16463                   struct Row9.2;
16464                   struct Row9.3;
16465                   struct Row8;
16466                   struct Row9;
16467                   struct Row10;"#},
16468        base_text,
16469        &mut cx,
16470    );
16471
16472    // When carets and selections intersect the addition hunks, those are reverted.
16473    // Adjacent carets got merged.
16474    assert_hunk_revert(
16475        indoc! {r#"struct Row;
16476                   ˇ// something on the top
16477                   struct Row1;
16478                   struct Row2;
16479                   struct Roˇw3.1;
16480                   struct Row2.2;
16481                   struct Row2.3;ˇ
16482
16483                   struct Row4;
16484                   struct ˇRow5.1;
16485                   struct Row5.2;
16486                   struct «Rowˇ»5.3;
16487                   struct Row5;
16488                   struct Row6;
16489                   ˇ
16490                   struct Row9.1;
16491                   struct «Rowˇ»9.2;
16492                   struct «ˇRow»9.3;
16493                   struct Row8;
16494                   struct Row9;
16495                   «ˇ// something on bottom»
16496                   struct Row10;"#},
16497        vec![
16498            DiffHunkStatusKind::Added,
16499            DiffHunkStatusKind::Added,
16500            DiffHunkStatusKind::Added,
16501            DiffHunkStatusKind::Added,
16502            DiffHunkStatusKind::Added,
16503        ],
16504        indoc! {r#"struct Row;
16505                   ˇstruct Row1;
16506                   struct Row2;
16507                   ˇ
16508                   struct Row4;
16509                   ˇstruct Row5;
16510                   struct Row6;
16511                   ˇ
16512                   ˇstruct Row8;
16513                   struct Row9;
16514                   ˇstruct Row10;"#},
16515        base_text,
16516        &mut cx,
16517    );
16518}
16519
16520#[gpui::test]
16521async fn test_modification_reverts(cx: &mut TestAppContext) {
16522    init_test(cx, |_| {});
16523    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16524    let base_text = indoc! {r#"
16525        struct Row;
16526        struct Row1;
16527        struct Row2;
16528
16529        struct Row4;
16530        struct Row5;
16531        struct Row6;
16532
16533        struct Row8;
16534        struct Row9;
16535        struct Row10;"#};
16536
16537    // Modification hunks behave the same as the addition ones.
16538    assert_hunk_revert(
16539        indoc! {r#"struct Row;
16540                   struct Row1;
16541                   struct Row33;
16542                   ˇ
16543                   struct Row4;
16544                   struct Row5;
16545                   struct Row6;
16546                   ˇ
16547                   struct Row99;
16548                   struct Row9;
16549                   struct Row10;"#},
16550        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16551        indoc! {r#"struct Row;
16552                   struct Row1;
16553                   struct Row33;
16554                   ˇ
16555                   struct Row4;
16556                   struct Row5;
16557                   struct Row6;
16558                   ˇ
16559                   struct Row99;
16560                   struct Row9;
16561                   struct Row10;"#},
16562        base_text,
16563        &mut cx,
16564    );
16565    assert_hunk_revert(
16566        indoc! {r#"struct Row;
16567                   struct Row1;
16568                   struct Row33;
16569                   «ˇ
16570                   struct Row4;
16571                   struct» Row5;
16572                   «struct Row6;
16573                   ˇ»
16574                   struct Row99;
16575                   struct Row9;
16576                   struct Row10;"#},
16577        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16578        indoc! {r#"struct Row;
16579                   struct Row1;
16580                   struct Row33;
16581                   «ˇ
16582                   struct Row4;
16583                   struct» Row5;
16584                   «struct Row6;
16585                   ˇ»
16586                   struct Row99;
16587                   struct Row9;
16588                   struct Row10;"#},
16589        base_text,
16590        &mut cx,
16591    );
16592
16593    assert_hunk_revert(
16594        indoc! {r#"ˇstruct Row1.1;
16595                   struct Row1;
16596                   «ˇstr»uct Row22;
16597
16598                   struct ˇRow44;
16599                   struct Row5;
16600                   struct «Rˇ»ow66;ˇ
16601
16602                   «struˇ»ct Row88;
16603                   struct Row9;
16604                   struct Row1011;ˇ"#},
16605        vec![
16606            DiffHunkStatusKind::Modified,
16607            DiffHunkStatusKind::Modified,
16608            DiffHunkStatusKind::Modified,
16609            DiffHunkStatusKind::Modified,
16610            DiffHunkStatusKind::Modified,
16611            DiffHunkStatusKind::Modified,
16612        ],
16613        indoc! {r#"struct Row;
16614                   ˇstruct Row1;
16615                   struct Row2;
16616                   ˇ
16617                   struct Row4;
16618                   ˇstruct Row5;
16619                   struct Row6;
16620                   ˇ
16621                   struct Row8;
16622                   ˇstruct Row9;
16623                   struct Row10;ˇ"#},
16624        base_text,
16625        &mut cx,
16626    );
16627}
16628
16629#[gpui::test]
16630async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16631    init_test(cx, |_| {});
16632    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16633    let base_text = indoc! {r#"
16634        one
16635
16636        two
16637        three
16638        "#};
16639
16640    cx.set_head_text(base_text);
16641    cx.set_state("\nˇ\n");
16642    cx.executor().run_until_parked();
16643    cx.update_editor(|editor, _window, cx| {
16644        editor.expand_selected_diff_hunks(cx);
16645    });
16646    cx.executor().run_until_parked();
16647    cx.update_editor(|editor, window, cx| {
16648        editor.backspace(&Default::default(), window, cx);
16649    });
16650    cx.run_until_parked();
16651    cx.assert_state_with_diff(
16652        indoc! {r#"
16653
16654        - two
16655        - threeˇ
16656        +
16657        "#}
16658        .to_string(),
16659    );
16660}
16661
16662#[gpui::test]
16663async fn test_deletion_reverts(cx: &mut TestAppContext) {
16664    init_test(cx, |_| {});
16665    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16666    let base_text = indoc! {r#"struct Row;
16667struct Row1;
16668struct Row2;
16669
16670struct Row4;
16671struct Row5;
16672struct Row6;
16673
16674struct Row8;
16675struct Row9;
16676struct Row10;"#};
16677
16678    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16679    assert_hunk_revert(
16680        indoc! {r#"struct Row;
16681                   struct Row2;
16682
16683                   ˇstruct Row4;
16684                   struct Row5;
16685                   struct Row6;
16686                   ˇ
16687                   struct Row8;
16688                   struct Row10;"#},
16689        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16690        indoc! {r#"struct Row;
16691                   struct Row2;
16692
16693                   ˇstruct Row4;
16694                   struct Row5;
16695                   struct Row6;
16696                   ˇ
16697                   struct Row8;
16698                   struct Row10;"#},
16699        base_text,
16700        &mut cx,
16701    );
16702    assert_hunk_revert(
16703        indoc! {r#"struct Row;
16704                   struct Row2;
16705
16706                   «ˇstruct Row4;
16707                   struct» Row5;
16708                   «struct Row6;
16709                   ˇ»
16710                   struct Row8;
16711                   struct Row10;"#},
16712        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16713        indoc! {r#"struct Row;
16714                   struct Row2;
16715
16716                   «ˇstruct Row4;
16717                   struct» Row5;
16718                   «struct Row6;
16719                   ˇ»
16720                   struct Row8;
16721                   struct Row10;"#},
16722        base_text,
16723        &mut cx,
16724    );
16725
16726    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16727    assert_hunk_revert(
16728        indoc! {r#"struct Row;
16729                   ˇstruct Row2;
16730
16731                   struct Row4;
16732                   struct Row5;
16733                   struct Row6;
16734
16735                   struct Row8;ˇ
16736                   struct Row10;"#},
16737        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16738        indoc! {r#"struct Row;
16739                   struct Row1;
16740                   ˇstruct Row2;
16741
16742                   struct Row4;
16743                   struct Row5;
16744                   struct Row6;
16745
16746                   struct Row8;ˇ
16747                   struct Row9;
16748                   struct Row10;"#},
16749        base_text,
16750        &mut cx,
16751    );
16752    assert_hunk_revert(
16753        indoc! {r#"struct Row;
16754                   struct Row2«ˇ;
16755                   struct Row4;
16756                   struct» Row5;
16757                   «struct Row6;
16758
16759                   struct Row8;ˇ»
16760                   struct Row10;"#},
16761        vec![
16762            DiffHunkStatusKind::Deleted,
16763            DiffHunkStatusKind::Deleted,
16764            DiffHunkStatusKind::Deleted,
16765        ],
16766        indoc! {r#"struct Row;
16767                   struct Row1;
16768                   struct Row2«ˇ;
16769
16770                   struct Row4;
16771                   struct» Row5;
16772                   «struct Row6;
16773
16774                   struct Row8;ˇ»
16775                   struct Row9;
16776                   struct Row10;"#},
16777        base_text,
16778        &mut cx,
16779    );
16780}
16781
16782#[gpui::test]
16783async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16784    init_test(cx, |_| {});
16785
16786    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16787    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16788    let base_text_3 =
16789        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16790
16791    let text_1 = edit_first_char_of_every_line(base_text_1);
16792    let text_2 = edit_first_char_of_every_line(base_text_2);
16793    let text_3 = edit_first_char_of_every_line(base_text_3);
16794
16795    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16796    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16797    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16798
16799    let multibuffer = cx.new(|cx| {
16800        let mut multibuffer = MultiBuffer::new(ReadWrite);
16801        multibuffer.push_excerpts(
16802            buffer_1.clone(),
16803            [
16804                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16805                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16806                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16807            ],
16808            cx,
16809        );
16810        multibuffer.push_excerpts(
16811            buffer_2.clone(),
16812            [
16813                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16814                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16815                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16816            ],
16817            cx,
16818        );
16819        multibuffer.push_excerpts(
16820            buffer_3.clone(),
16821            [
16822                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16823                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16824                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16825            ],
16826            cx,
16827        );
16828        multibuffer
16829    });
16830
16831    let fs = FakeFs::new(cx.executor());
16832    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16833    let (editor, cx) = cx
16834        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16835    editor.update_in(cx, |editor, _window, cx| {
16836        for (buffer, diff_base) in [
16837            (buffer_1.clone(), base_text_1),
16838            (buffer_2.clone(), base_text_2),
16839            (buffer_3.clone(), base_text_3),
16840        ] {
16841            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16842            editor
16843                .buffer
16844                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16845        }
16846    });
16847    cx.executor().run_until_parked();
16848
16849    editor.update_in(cx, |editor, window, cx| {
16850        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}");
16851        editor.select_all(&SelectAll, window, cx);
16852        editor.git_restore(&Default::default(), window, cx);
16853    });
16854    cx.executor().run_until_parked();
16855
16856    // When all ranges are selected, all buffer hunks are reverted.
16857    editor.update(cx, |editor, cx| {
16858        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");
16859    });
16860    buffer_1.update(cx, |buffer, _| {
16861        assert_eq!(buffer.text(), base_text_1);
16862    });
16863    buffer_2.update(cx, |buffer, _| {
16864        assert_eq!(buffer.text(), base_text_2);
16865    });
16866    buffer_3.update(cx, |buffer, _| {
16867        assert_eq!(buffer.text(), base_text_3);
16868    });
16869
16870    editor.update_in(cx, |editor, window, cx| {
16871        editor.undo(&Default::default(), window, cx);
16872    });
16873
16874    editor.update_in(cx, |editor, window, cx| {
16875        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16876            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16877        });
16878        editor.git_restore(&Default::default(), window, cx);
16879    });
16880
16881    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16882    // but not affect buffer_2 and its related excerpts.
16883    editor.update(cx, |editor, cx| {
16884        assert_eq!(
16885            editor.text(cx),
16886            "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}"
16887        );
16888    });
16889    buffer_1.update(cx, |buffer, _| {
16890        assert_eq!(buffer.text(), base_text_1);
16891    });
16892    buffer_2.update(cx, |buffer, _| {
16893        assert_eq!(
16894            buffer.text(),
16895            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16896        );
16897    });
16898    buffer_3.update(cx, |buffer, _| {
16899        assert_eq!(
16900            buffer.text(),
16901            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16902        );
16903    });
16904
16905    fn edit_first_char_of_every_line(text: &str) -> String {
16906        text.split('\n')
16907            .map(|line| format!("X{}", &line[1..]))
16908            .collect::<Vec<_>>()
16909            .join("\n")
16910    }
16911}
16912
16913#[gpui::test]
16914async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
16915    init_test(cx, |_| {});
16916
16917    let cols = 4;
16918    let rows = 10;
16919    let sample_text_1 = sample_text(rows, cols, 'a');
16920    assert_eq!(
16921        sample_text_1,
16922        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16923    );
16924    let sample_text_2 = sample_text(rows, cols, 'l');
16925    assert_eq!(
16926        sample_text_2,
16927        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16928    );
16929    let sample_text_3 = sample_text(rows, cols, 'v');
16930    assert_eq!(
16931        sample_text_3,
16932        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16933    );
16934
16935    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16936    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16937    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16938
16939    let multi_buffer = cx.new(|cx| {
16940        let mut multibuffer = MultiBuffer::new(ReadWrite);
16941        multibuffer.push_excerpts(
16942            buffer_1.clone(),
16943            [
16944                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16945                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16946                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16947            ],
16948            cx,
16949        );
16950        multibuffer.push_excerpts(
16951            buffer_2.clone(),
16952            [
16953                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16954                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16955                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16956            ],
16957            cx,
16958        );
16959        multibuffer.push_excerpts(
16960            buffer_3.clone(),
16961            [
16962                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16963                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16964                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16965            ],
16966            cx,
16967        );
16968        multibuffer
16969    });
16970
16971    let fs = FakeFs::new(cx.executor());
16972    fs.insert_tree(
16973        "/a",
16974        json!({
16975            "main.rs": sample_text_1,
16976            "other.rs": sample_text_2,
16977            "lib.rs": sample_text_3,
16978        }),
16979    )
16980    .await;
16981    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16982    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16983    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16984    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16985        Editor::new(
16986            EditorMode::full(),
16987            multi_buffer,
16988            Some(project.clone()),
16989            window,
16990            cx,
16991        )
16992    });
16993    let multibuffer_item_id = workspace
16994        .update(cx, |workspace, window, cx| {
16995            assert!(
16996                workspace.active_item(cx).is_none(),
16997                "active item should be None before the first item is added"
16998            );
16999            workspace.add_item_to_active_pane(
17000                Box::new(multi_buffer_editor.clone()),
17001                None,
17002                true,
17003                window,
17004                cx,
17005            );
17006            let active_item = workspace
17007                .active_item(cx)
17008                .expect("should have an active item after adding the multi buffer");
17009            assert!(
17010                !active_item.is_singleton(cx),
17011                "A multi buffer was expected to active after adding"
17012            );
17013            active_item.item_id()
17014        })
17015        .unwrap();
17016    cx.executor().run_until_parked();
17017
17018    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17019        editor.change_selections(
17020            SelectionEffects::scroll(Autoscroll::Next),
17021            window,
17022            cx,
17023            |s| s.select_ranges(Some(1..2)),
17024        );
17025        editor.open_excerpts(&OpenExcerpts, window, cx);
17026    });
17027    cx.executor().run_until_parked();
17028    let first_item_id = workspace
17029        .update(cx, |workspace, window, cx| {
17030            let active_item = workspace
17031                .active_item(cx)
17032                .expect("should have an active item after navigating into the 1st buffer");
17033            let first_item_id = active_item.item_id();
17034            assert_ne!(
17035                first_item_id, multibuffer_item_id,
17036                "Should navigate into the 1st buffer and activate it"
17037            );
17038            assert!(
17039                active_item.is_singleton(cx),
17040                "New active item should be a singleton buffer"
17041            );
17042            assert_eq!(
17043                active_item
17044                    .act_as::<Editor>(cx)
17045                    .expect("should have navigated into an editor for the 1st buffer")
17046                    .read(cx)
17047                    .text(cx),
17048                sample_text_1
17049            );
17050
17051            workspace
17052                .go_back(workspace.active_pane().downgrade(), window, cx)
17053                .detach_and_log_err(cx);
17054
17055            first_item_id
17056        })
17057        .unwrap();
17058    cx.executor().run_until_parked();
17059    workspace
17060        .update(cx, |workspace, _, cx| {
17061            let active_item = workspace
17062                .active_item(cx)
17063                .expect("should have an active item after navigating back");
17064            assert_eq!(
17065                active_item.item_id(),
17066                multibuffer_item_id,
17067                "Should navigate back to the multi buffer"
17068            );
17069            assert!(!active_item.is_singleton(cx));
17070        })
17071        .unwrap();
17072
17073    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17074        editor.change_selections(
17075            SelectionEffects::scroll(Autoscroll::Next),
17076            window,
17077            cx,
17078            |s| s.select_ranges(Some(39..40)),
17079        );
17080        editor.open_excerpts(&OpenExcerpts, window, cx);
17081    });
17082    cx.executor().run_until_parked();
17083    let second_item_id = workspace
17084        .update(cx, |workspace, window, cx| {
17085            let active_item = workspace
17086                .active_item(cx)
17087                .expect("should have an active item after navigating into the 2nd buffer");
17088            let second_item_id = active_item.item_id();
17089            assert_ne!(
17090                second_item_id, multibuffer_item_id,
17091                "Should navigate away from the multibuffer"
17092            );
17093            assert_ne!(
17094                second_item_id, first_item_id,
17095                "Should navigate into the 2nd buffer and activate it"
17096            );
17097            assert!(
17098                active_item.is_singleton(cx),
17099                "New active item should be a singleton buffer"
17100            );
17101            assert_eq!(
17102                active_item
17103                    .act_as::<Editor>(cx)
17104                    .expect("should have navigated into an editor")
17105                    .read(cx)
17106                    .text(cx),
17107                sample_text_2
17108            );
17109
17110            workspace
17111                .go_back(workspace.active_pane().downgrade(), window, cx)
17112                .detach_and_log_err(cx);
17113
17114            second_item_id
17115        })
17116        .unwrap();
17117    cx.executor().run_until_parked();
17118    workspace
17119        .update(cx, |workspace, _, cx| {
17120            let active_item = workspace
17121                .active_item(cx)
17122                .expect("should have an active item after navigating back from the 2nd buffer");
17123            assert_eq!(
17124                active_item.item_id(),
17125                multibuffer_item_id,
17126                "Should navigate back from the 2nd buffer to the multi buffer"
17127            );
17128            assert!(!active_item.is_singleton(cx));
17129        })
17130        .unwrap();
17131
17132    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17133        editor.change_selections(
17134            SelectionEffects::scroll(Autoscroll::Next),
17135            window,
17136            cx,
17137            |s| s.select_ranges(Some(70..70)),
17138        );
17139        editor.open_excerpts(&OpenExcerpts, window, cx);
17140    });
17141    cx.executor().run_until_parked();
17142    workspace
17143        .update(cx, |workspace, window, cx| {
17144            let active_item = workspace
17145                .active_item(cx)
17146                .expect("should have an active item after navigating into the 3rd buffer");
17147            let third_item_id = active_item.item_id();
17148            assert_ne!(
17149                third_item_id, multibuffer_item_id,
17150                "Should navigate into the 3rd buffer and activate it"
17151            );
17152            assert_ne!(third_item_id, first_item_id);
17153            assert_ne!(third_item_id, second_item_id);
17154            assert!(
17155                active_item.is_singleton(cx),
17156                "New active item should be a singleton buffer"
17157            );
17158            assert_eq!(
17159                active_item
17160                    .act_as::<Editor>(cx)
17161                    .expect("should have navigated into an editor")
17162                    .read(cx)
17163                    .text(cx),
17164                sample_text_3
17165            );
17166
17167            workspace
17168                .go_back(workspace.active_pane().downgrade(), window, cx)
17169                .detach_and_log_err(cx);
17170        })
17171        .unwrap();
17172    cx.executor().run_until_parked();
17173    workspace
17174        .update(cx, |workspace, _, cx| {
17175            let active_item = workspace
17176                .active_item(cx)
17177                .expect("should have an active item after navigating back from the 3rd buffer");
17178            assert_eq!(
17179                active_item.item_id(),
17180                multibuffer_item_id,
17181                "Should navigate back from the 3rd buffer to the multi buffer"
17182            );
17183            assert!(!active_item.is_singleton(cx));
17184        })
17185        .unwrap();
17186}
17187
17188#[gpui::test]
17189async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17190    init_test(cx, |_| {});
17191
17192    let mut cx = EditorTestContext::new(cx).await;
17193
17194    let diff_base = r#"
17195        use some::mod;
17196
17197        const A: u32 = 42;
17198
17199        fn main() {
17200            println!("hello");
17201
17202            println!("world");
17203        }
17204        "#
17205    .unindent();
17206
17207    cx.set_state(
17208        &r#"
17209        use some::modified;
17210
17211        ˇ
17212        fn main() {
17213            println!("hello there");
17214
17215            println!("around the");
17216            println!("world");
17217        }
17218        "#
17219        .unindent(),
17220    );
17221
17222    cx.set_head_text(&diff_base);
17223    executor.run_until_parked();
17224
17225    cx.update_editor(|editor, window, cx| {
17226        editor.go_to_next_hunk(&GoToHunk, window, cx);
17227        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17228    });
17229    executor.run_until_parked();
17230    cx.assert_state_with_diff(
17231        r#"
17232          use some::modified;
17233
17234
17235          fn main() {
17236        -     println!("hello");
17237        + ˇ    println!("hello there");
17238
17239              println!("around the");
17240              println!("world");
17241          }
17242        "#
17243        .unindent(),
17244    );
17245
17246    cx.update_editor(|editor, window, cx| {
17247        for _ in 0..2 {
17248            editor.go_to_next_hunk(&GoToHunk, window, cx);
17249            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17250        }
17251    });
17252    executor.run_until_parked();
17253    cx.assert_state_with_diff(
17254        r#"
17255        - use some::mod;
17256        + ˇuse some::modified;
17257
17258
17259          fn main() {
17260        -     println!("hello");
17261        +     println!("hello there");
17262
17263        +     println!("around the");
17264              println!("world");
17265          }
17266        "#
17267        .unindent(),
17268    );
17269
17270    cx.update_editor(|editor, window, cx| {
17271        editor.go_to_next_hunk(&GoToHunk, window, cx);
17272        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17273    });
17274    executor.run_until_parked();
17275    cx.assert_state_with_diff(
17276        r#"
17277        - use some::mod;
17278        + use some::modified;
17279
17280        - const A: u32 = 42;
17281          ˇ
17282          fn main() {
17283        -     println!("hello");
17284        +     println!("hello there");
17285
17286        +     println!("around the");
17287              println!("world");
17288          }
17289        "#
17290        .unindent(),
17291    );
17292
17293    cx.update_editor(|editor, window, cx| {
17294        editor.cancel(&Cancel, window, cx);
17295    });
17296
17297    cx.assert_state_with_diff(
17298        r#"
17299          use some::modified;
17300
17301          ˇ
17302          fn main() {
17303              println!("hello there");
17304
17305              println!("around the");
17306              println!("world");
17307          }
17308        "#
17309        .unindent(),
17310    );
17311}
17312
17313#[gpui::test]
17314async fn test_diff_base_change_with_expanded_diff_hunks(
17315    executor: BackgroundExecutor,
17316    cx: &mut TestAppContext,
17317) {
17318    init_test(cx, |_| {});
17319
17320    let mut cx = EditorTestContext::new(cx).await;
17321
17322    let diff_base = r#"
17323        use some::mod1;
17324        use some::mod2;
17325
17326        const A: u32 = 42;
17327        const B: u32 = 42;
17328        const C: u32 = 42;
17329
17330        fn main() {
17331            println!("hello");
17332
17333            println!("world");
17334        }
17335        "#
17336    .unindent();
17337
17338    cx.set_state(
17339        &r#"
17340        use some::mod2;
17341
17342        const A: u32 = 42;
17343        const C: u32 = 42;
17344
17345        fn main(ˇ) {
17346            //println!("hello");
17347
17348            println!("world");
17349            //
17350            //
17351        }
17352        "#
17353        .unindent(),
17354    );
17355
17356    cx.set_head_text(&diff_base);
17357    executor.run_until_parked();
17358
17359    cx.update_editor(|editor, window, cx| {
17360        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17361    });
17362    executor.run_until_parked();
17363    cx.assert_state_with_diff(
17364        r#"
17365        - use some::mod1;
17366          use some::mod2;
17367
17368          const A: u32 = 42;
17369        - const B: u32 = 42;
17370          const C: u32 = 42;
17371
17372          fn main(ˇ) {
17373        -     println!("hello");
17374        +     //println!("hello");
17375
17376              println!("world");
17377        +     //
17378        +     //
17379          }
17380        "#
17381        .unindent(),
17382    );
17383
17384    cx.set_head_text("new diff base!");
17385    executor.run_until_parked();
17386    cx.assert_state_with_diff(
17387        r#"
17388        - new diff base!
17389        + use some::mod2;
17390        +
17391        + const A: u32 = 42;
17392        + const C: u32 = 42;
17393        +
17394        + fn main(ˇ) {
17395        +     //println!("hello");
17396        +
17397        +     println!("world");
17398        +     //
17399        +     //
17400        + }
17401        "#
17402        .unindent(),
17403    );
17404}
17405
17406#[gpui::test]
17407async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17408    init_test(cx, |_| {});
17409
17410    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17411    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17412    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17413    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17414    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17415    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17416
17417    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17418    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17419    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17420
17421    let multi_buffer = cx.new(|cx| {
17422        let mut multibuffer = MultiBuffer::new(ReadWrite);
17423        multibuffer.push_excerpts(
17424            buffer_1.clone(),
17425            [
17426                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17427                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17428                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17429            ],
17430            cx,
17431        );
17432        multibuffer.push_excerpts(
17433            buffer_2.clone(),
17434            [
17435                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17436                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17437                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17438            ],
17439            cx,
17440        );
17441        multibuffer.push_excerpts(
17442            buffer_3.clone(),
17443            [
17444                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17445                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17446                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17447            ],
17448            cx,
17449        );
17450        multibuffer
17451    });
17452
17453    let editor =
17454        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17455    editor
17456        .update(cx, |editor, _window, cx| {
17457            for (buffer, diff_base) in [
17458                (buffer_1.clone(), file_1_old),
17459                (buffer_2.clone(), file_2_old),
17460                (buffer_3.clone(), file_3_old),
17461            ] {
17462                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17463                editor
17464                    .buffer
17465                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17466            }
17467        })
17468        .unwrap();
17469
17470    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17471    cx.run_until_parked();
17472
17473    cx.assert_editor_state(
17474        &"
17475            ˇaaa
17476            ccc
17477            ddd
17478
17479            ggg
17480            hhh
17481
17482
17483            lll
17484            mmm
17485            NNN
17486
17487            qqq
17488            rrr
17489
17490            uuu
17491            111
17492            222
17493            333
17494
17495            666
17496            777
17497
17498            000
17499            !!!"
17500        .unindent(),
17501    );
17502
17503    cx.update_editor(|editor, window, cx| {
17504        editor.select_all(&SelectAll, window, cx);
17505        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17506    });
17507    cx.executor().run_until_parked();
17508
17509    cx.assert_state_with_diff(
17510        "
17511            «aaa
17512          - bbb
17513            ccc
17514            ddd
17515
17516            ggg
17517            hhh
17518
17519
17520            lll
17521            mmm
17522          - nnn
17523          + NNN
17524
17525            qqq
17526            rrr
17527
17528            uuu
17529            111
17530            222
17531            333
17532
17533          + 666
17534            777
17535
17536            000
17537            !!!ˇ»"
17538            .unindent(),
17539    );
17540}
17541
17542#[gpui::test]
17543async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17544    init_test(cx, |_| {});
17545
17546    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17547    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17548
17549    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17550    let multi_buffer = cx.new(|cx| {
17551        let mut multibuffer = MultiBuffer::new(ReadWrite);
17552        multibuffer.push_excerpts(
17553            buffer.clone(),
17554            [
17555                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17556                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17557                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17558            ],
17559            cx,
17560        );
17561        multibuffer
17562    });
17563
17564    let editor =
17565        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17566    editor
17567        .update(cx, |editor, _window, cx| {
17568            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17569            editor
17570                .buffer
17571                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17572        })
17573        .unwrap();
17574
17575    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17576    cx.run_until_parked();
17577
17578    cx.update_editor(|editor, window, cx| {
17579        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17580    });
17581    cx.executor().run_until_parked();
17582
17583    // When the start of a hunk coincides with the start of its excerpt,
17584    // the hunk is expanded. When the start of a a hunk is earlier than
17585    // the start of its excerpt, the hunk is not expanded.
17586    cx.assert_state_with_diff(
17587        "
17588            ˇaaa
17589          - bbb
17590          + BBB
17591
17592          - ddd
17593          - eee
17594          + DDD
17595          + EEE
17596            fff
17597
17598            iii
17599        "
17600        .unindent(),
17601    );
17602}
17603
17604#[gpui::test]
17605async fn test_edits_around_expanded_insertion_hunks(
17606    executor: BackgroundExecutor,
17607    cx: &mut TestAppContext,
17608) {
17609    init_test(cx, |_| {});
17610
17611    let mut cx = EditorTestContext::new(cx).await;
17612
17613    let diff_base = r#"
17614        use some::mod1;
17615        use some::mod2;
17616
17617        const A: u32 = 42;
17618
17619        fn main() {
17620            println!("hello");
17621
17622            println!("world");
17623        }
17624        "#
17625    .unindent();
17626    executor.run_until_parked();
17627    cx.set_state(
17628        &r#"
17629        use some::mod1;
17630        use some::mod2;
17631
17632        const A: u32 = 42;
17633        const B: u32 = 42;
17634        const C: u32 = 42;
17635        ˇ
17636
17637        fn main() {
17638            println!("hello");
17639
17640            println!("world");
17641        }
17642        "#
17643        .unindent(),
17644    );
17645
17646    cx.set_head_text(&diff_base);
17647    executor.run_until_parked();
17648
17649    cx.update_editor(|editor, window, cx| {
17650        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17651    });
17652    executor.run_until_parked();
17653
17654    cx.assert_state_with_diff(
17655        r#"
17656        use some::mod1;
17657        use some::mod2;
17658
17659        const A: u32 = 42;
17660      + const B: u32 = 42;
17661      + const C: u32 = 42;
17662      + ˇ
17663
17664        fn main() {
17665            println!("hello");
17666
17667            println!("world");
17668        }
17669      "#
17670        .unindent(),
17671    );
17672
17673    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17674    executor.run_until_parked();
17675
17676    cx.assert_state_with_diff(
17677        r#"
17678        use some::mod1;
17679        use some::mod2;
17680
17681        const A: u32 = 42;
17682      + const B: u32 = 42;
17683      + const C: u32 = 42;
17684      + const D: u32 = 42;
17685      + ˇ
17686
17687        fn main() {
17688            println!("hello");
17689
17690            println!("world");
17691        }
17692      "#
17693        .unindent(),
17694    );
17695
17696    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17697    executor.run_until_parked();
17698
17699    cx.assert_state_with_diff(
17700        r#"
17701        use some::mod1;
17702        use some::mod2;
17703
17704        const A: u32 = 42;
17705      + const B: u32 = 42;
17706      + const C: u32 = 42;
17707      + const D: u32 = 42;
17708      + const E: u32 = 42;
17709      + ˇ
17710
17711        fn main() {
17712            println!("hello");
17713
17714            println!("world");
17715        }
17716      "#
17717        .unindent(),
17718    );
17719
17720    cx.update_editor(|editor, window, cx| {
17721        editor.delete_line(&DeleteLine, window, cx);
17722    });
17723    executor.run_until_parked();
17724
17725    cx.assert_state_with_diff(
17726        r#"
17727        use some::mod1;
17728        use some::mod2;
17729
17730        const A: u32 = 42;
17731      + const B: u32 = 42;
17732      + const C: u32 = 42;
17733      + const D: u32 = 42;
17734      + const E: u32 = 42;
17735        ˇ
17736        fn main() {
17737            println!("hello");
17738
17739            println!("world");
17740        }
17741      "#
17742        .unindent(),
17743    );
17744
17745    cx.update_editor(|editor, window, cx| {
17746        editor.move_up(&MoveUp, window, cx);
17747        editor.delete_line(&DeleteLine, window, cx);
17748        editor.move_up(&MoveUp, window, cx);
17749        editor.delete_line(&DeleteLine, window, cx);
17750        editor.move_up(&MoveUp, window, cx);
17751        editor.delete_line(&DeleteLine, window, cx);
17752    });
17753    executor.run_until_parked();
17754    cx.assert_state_with_diff(
17755        r#"
17756        use some::mod1;
17757        use some::mod2;
17758
17759        const A: u32 = 42;
17760      + const B: u32 = 42;
17761        ˇ
17762        fn main() {
17763            println!("hello");
17764
17765            println!("world");
17766        }
17767      "#
17768        .unindent(),
17769    );
17770
17771    cx.update_editor(|editor, window, cx| {
17772        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17773        editor.delete_line(&DeleteLine, window, cx);
17774    });
17775    executor.run_until_parked();
17776    cx.assert_state_with_diff(
17777        r#"
17778        ˇ
17779        fn main() {
17780            println!("hello");
17781
17782            println!("world");
17783        }
17784      "#
17785        .unindent(),
17786    );
17787}
17788
17789#[gpui::test]
17790async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17791    init_test(cx, |_| {});
17792
17793    let mut cx = EditorTestContext::new(cx).await;
17794    cx.set_head_text(indoc! { "
17795        one
17796        two
17797        three
17798        four
17799        five
17800        "
17801    });
17802    cx.set_state(indoc! { "
17803        one
17804        ˇthree
17805        five
17806    "});
17807    cx.run_until_parked();
17808    cx.update_editor(|editor, window, cx| {
17809        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17810    });
17811    cx.assert_state_with_diff(
17812        indoc! { "
17813        one
17814      - two
17815        ˇthree
17816      - four
17817        five
17818    "}
17819        .to_string(),
17820    );
17821    cx.update_editor(|editor, window, cx| {
17822        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17823    });
17824
17825    cx.assert_state_with_diff(
17826        indoc! { "
17827        one
17828        ˇthree
17829        five
17830    "}
17831        .to_string(),
17832    );
17833
17834    cx.set_state(indoc! { "
17835        one
17836        ˇTWO
17837        three
17838        four
17839        five
17840    "});
17841    cx.run_until_parked();
17842    cx.update_editor(|editor, window, cx| {
17843        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17844    });
17845
17846    cx.assert_state_with_diff(
17847        indoc! { "
17848            one
17849          - two
17850          + ˇTWO
17851            three
17852            four
17853            five
17854        "}
17855        .to_string(),
17856    );
17857    cx.update_editor(|editor, window, cx| {
17858        editor.move_up(&Default::default(), window, cx);
17859        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17860    });
17861    cx.assert_state_with_diff(
17862        indoc! { "
17863            one
17864            ˇTWO
17865            three
17866            four
17867            five
17868        "}
17869        .to_string(),
17870    );
17871}
17872
17873#[gpui::test]
17874async fn test_edits_around_expanded_deletion_hunks(
17875    executor: BackgroundExecutor,
17876    cx: &mut TestAppContext,
17877) {
17878    init_test(cx, |_| {});
17879
17880    let mut cx = EditorTestContext::new(cx).await;
17881
17882    let diff_base = r#"
17883        use some::mod1;
17884        use some::mod2;
17885
17886        const A: u32 = 42;
17887        const B: u32 = 42;
17888        const C: u32 = 42;
17889
17890
17891        fn main() {
17892            println!("hello");
17893
17894            println!("world");
17895        }
17896    "#
17897    .unindent();
17898    executor.run_until_parked();
17899    cx.set_state(
17900        &r#"
17901        use some::mod1;
17902        use some::mod2;
17903
17904        ˇconst B: u32 = 42;
17905        const C: u32 = 42;
17906
17907
17908        fn main() {
17909            println!("hello");
17910
17911            println!("world");
17912        }
17913        "#
17914        .unindent(),
17915    );
17916
17917    cx.set_head_text(&diff_base);
17918    executor.run_until_parked();
17919
17920    cx.update_editor(|editor, window, cx| {
17921        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17922    });
17923    executor.run_until_parked();
17924
17925    cx.assert_state_with_diff(
17926        r#"
17927        use some::mod1;
17928        use some::mod2;
17929
17930      - const A: u32 = 42;
17931        ˇconst B: u32 = 42;
17932        const C: u32 = 42;
17933
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.delete_line(&DeleteLine, window, cx);
17946    });
17947    executor.run_until_parked();
17948    cx.assert_state_with_diff(
17949        r#"
17950        use some::mod1;
17951        use some::mod2;
17952
17953      - const A: u32 = 42;
17954      - const B: u32 = 42;
17955        ˇconst C: u32 = 42;
17956
17957
17958        fn main() {
17959            println!("hello");
17960
17961            println!("world");
17962        }
17963      "#
17964        .unindent(),
17965    );
17966
17967    cx.update_editor(|editor, window, cx| {
17968        editor.delete_line(&DeleteLine, window, cx);
17969    });
17970    executor.run_until_parked();
17971    cx.assert_state_with_diff(
17972        r#"
17973        use some::mod1;
17974        use some::mod2;
17975
17976      - const A: u32 = 42;
17977      - const B: u32 = 42;
17978      - const C: u32 = 42;
17979        ˇ
17980
17981        fn main() {
17982            println!("hello");
17983
17984            println!("world");
17985        }
17986      "#
17987        .unindent(),
17988    );
17989
17990    cx.update_editor(|editor, window, cx| {
17991        editor.handle_input("replacement", window, cx);
17992    });
17993    executor.run_until_parked();
17994    cx.assert_state_with_diff(
17995        r#"
17996        use some::mod1;
17997        use some::mod2;
17998
17999      - const A: u32 = 42;
18000      - const B: u32 = 42;
18001      - const C: u32 = 42;
18002      -
18003      + replacementˇ
18004
18005        fn main() {
18006            println!("hello");
18007
18008            println!("world");
18009        }
18010      "#
18011        .unindent(),
18012    );
18013}
18014
18015#[gpui::test]
18016async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18017    init_test(cx, |_| {});
18018
18019    let mut cx = EditorTestContext::new(cx).await;
18020
18021    let base_text = r#"
18022        one
18023        two
18024        three
18025        four
18026        five
18027    "#
18028    .unindent();
18029    executor.run_until_parked();
18030    cx.set_state(
18031        &r#"
18032        one
18033        two
18034        fˇour
18035        five
18036        "#
18037        .unindent(),
18038    );
18039
18040    cx.set_head_text(&base_text);
18041    executor.run_until_parked();
18042
18043    cx.update_editor(|editor, window, cx| {
18044        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18045    });
18046    executor.run_until_parked();
18047
18048    cx.assert_state_with_diff(
18049        r#"
18050          one
18051          two
18052        - three
18053          fˇour
18054          five
18055        "#
18056        .unindent(),
18057    );
18058
18059    cx.update_editor(|editor, window, cx| {
18060        editor.backspace(&Backspace, window, cx);
18061        editor.backspace(&Backspace, window, cx);
18062    });
18063    executor.run_until_parked();
18064    cx.assert_state_with_diff(
18065        r#"
18066          one
18067          two
18068        - threeˇ
18069        - four
18070        + our
18071          five
18072        "#
18073        .unindent(),
18074    );
18075}
18076
18077#[gpui::test]
18078async fn test_edit_after_expanded_modification_hunk(
18079    executor: BackgroundExecutor,
18080    cx: &mut TestAppContext,
18081) {
18082    init_test(cx, |_| {});
18083
18084    let mut cx = EditorTestContext::new(cx).await;
18085
18086    let diff_base = r#"
18087        use some::mod1;
18088        use some::mod2;
18089
18090        const A: u32 = 42;
18091        const B: u32 = 42;
18092        const C: u32 = 42;
18093        const D: u32 = 42;
18094
18095
18096        fn main() {
18097            println!("hello");
18098
18099            println!("world");
18100        }"#
18101    .unindent();
18102
18103    cx.set_state(
18104        &r#"
18105        use some::mod1;
18106        use some::mod2;
18107
18108        const A: u32 = 42;
18109        const B: u32 = 42;
18110        const C: u32 = 43ˇ
18111        const D: u32 = 42;
18112
18113
18114        fn main() {
18115            println!("hello");
18116
18117            println!("world");
18118        }"#
18119        .unindent(),
18120    );
18121
18122    cx.set_head_text(&diff_base);
18123    executor.run_until_parked();
18124    cx.update_editor(|editor, window, cx| {
18125        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18126    });
18127    executor.run_until_parked();
18128
18129    cx.assert_state_with_diff(
18130        r#"
18131        use some::mod1;
18132        use some::mod2;
18133
18134        const A: u32 = 42;
18135        const B: u32 = 42;
18136      - const C: u32 = 42;
18137      + const C: u32 = 43ˇ
18138        const D: u32 = 42;
18139
18140
18141        fn main() {
18142            println!("hello");
18143
18144            println!("world");
18145        }"#
18146        .unindent(),
18147    );
18148
18149    cx.update_editor(|editor, window, cx| {
18150        editor.handle_input("\nnew_line\n", window, cx);
18151    });
18152    executor.run_until_parked();
18153
18154    cx.assert_state_with_diff(
18155        r#"
18156        use some::mod1;
18157        use some::mod2;
18158
18159        const A: u32 = 42;
18160        const B: u32 = 42;
18161      - const C: u32 = 42;
18162      + const C: u32 = 43
18163      + new_line
18164      + ˇ
18165        const D: u32 = 42;
18166
18167
18168        fn main() {
18169            println!("hello");
18170
18171            println!("world");
18172        }"#
18173        .unindent(),
18174    );
18175}
18176
18177#[gpui::test]
18178async fn test_stage_and_unstage_added_file_hunk(
18179    executor: BackgroundExecutor,
18180    cx: &mut TestAppContext,
18181) {
18182    init_test(cx, |_| {});
18183
18184    let mut cx = EditorTestContext::new(cx).await;
18185    cx.update_editor(|editor, _, cx| {
18186        editor.set_expand_all_diff_hunks(cx);
18187    });
18188
18189    let working_copy = r#"
18190            ˇfn main() {
18191                println!("hello, world!");
18192            }
18193        "#
18194    .unindent();
18195
18196    cx.set_state(&working_copy);
18197    executor.run_until_parked();
18198
18199    cx.assert_state_with_diff(
18200        r#"
18201            + ˇfn main() {
18202            +     println!("hello, world!");
18203            + }
18204        "#
18205        .unindent(),
18206    );
18207    cx.assert_index_text(None);
18208
18209    cx.update_editor(|editor, window, cx| {
18210        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18211    });
18212    executor.run_until_parked();
18213    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18214    cx.assert_state_with_diff(
18215        r#"
18216            + ˇfn main() {
18217            +     println!("hello, world!");
18218            + }
18219        "#
18220        .unindent(),
18221    );
18222
18223    cx.update_editor(|editor, window, cx| {
18224        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18225    });
18226    executor.run_until_parked();
18227    cx.assert_index_text(None);
18228}
18229
18230async fn setup_indent_guides_editor(
18231    text: &str,
18232    cx: &mut TestAppContext,
18233) -> (BufferId, EditorTestContext) {
18234    init_test(cx, |_| {});
18235
18236    let mut cx = EditorTestContext::new(cx).await;
18237
18238    let buffer_id = cx.update_editor(|editor, window, cx| {
18239        editor.set_text(text, window, cx);
18240        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18241
18242        buffer_ids[0]
18243    });
18244
18245    (buffer_id, cx)
18246}
18247
18248fn assert_indent_guides(
18249    range: Range<u32>,
18250    expected: Vec<IndentGuide>,
18251    active_indices: Option<Vec<usize>>,
18252    cx: &mut EditorTestContext,
18253) {
18254    let indent_guides = cx.update_editor(|editor, window, cx| {
18255        let snapshot = editor.snapshot(window, cx).display_snapshot;
18256        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18257            editor,
18258            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18259            true,
18260            &snapshot,
18261            cx,
18262        );
18263
18264        indent_guides.sort_by(|a, b| {
18265            a.depth.cmp(&b.depth).then(
18266                a.start_row
18267                    .cmp(&b.start_row)
18268                    .then(a.end_row.cmp(&b.end_row)),
18269            )
18270        });
18271        indent_guides
18272    });
18273
18274    if let Some(expected) = active_indices {
18275        let active_indices = cx.update_editor(|editor, window, cx| {
18276            let snapshot = editor.snapshot(window, cx).display_snapshot;
18277            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18278        });
18279
18280        assert_eq!(
18281            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18282            expected,
18283            "Active indent guide indices do not match"
18284        );
18285    }
18286
18287    assert_eq!(indent_guides, expected, "Indent guides do not match");
18288}
18289
18290fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18291    IndentGuide {
18292        buffer_id,
18293        start_row: MultiBufferRow(start_row),
18294        end_row: MultiBufferRow(end_row),
18295        depth,
18296        tab_size: 4,
18297        settings: IndentGuideSettings {
18298            enabled: true,
18299            line_width: 1,
18300            active_line_width: 1,
18301            ..Default::default()
18302        },
18303    }
18304}
18305
18306#[gpui::test]
18307async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18308    let (buffer_id, mut cx) = setup_indent_guides_editor(
18309        &"
18310        fn main() {
18311            let a = 1;
18312        }"
18313        .unindent(),
18314        cx,
18315    )
18316    .await;
18317
18318    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18319}
18320
18321#[gpui::test]
18322async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18323    let (buffer_id, mut cx) = setup_indent_guides_editor(
18324        &"
18325        fn main() {
18326            let a = 1;
18327            let b = 2;
18328        }"
18329        .unindent(),
18330        cx,
18331    )
18332    .await;
18333
18334    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18335}
18336
18337#[gpui::test]
18338async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18339    let (buffer_id, mut cx) = setup_indent_guides_editor(
18340        &"
18341        fn main() {
18342            let a = 1;
18343            if a == 3 {
18344                let b = 2;
18345            } else {
18346                let c = 3;
18347            }
18348        }"
18349        .unindent(),
18350        cx,
18351    )
18352    .await;
18353
18354    assert_indent_guides(
18355        0..8,
18356        vec![
18357            indent_guide(buffer_id, 1, 6, 0),
18358            indent_guide(buffer_id, 3, 3, 1),
18359            indent_guide(buffer_id, 5, 5, 1),
18360        ],
18361        None,
18362        &mut cx,
18363    );
18364}
18365
18366#[gpui::test]
18367async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18368    let (buffer_id, mut cx) = setup_indent_guides_editor(
18369        &"
18370        fn main() {
18371            let a = 1;
18372                let b = 2;
18373            let c = 3;
18374        }"
18375        .unindent(),
18376        cx,
18377    )
18378    .await;
18379
18380    assert_indent_guides(
18381        0..5,
18382        vec![
18383            indent_guide(buffer_id, 1, 3, 0),
18384            indent_guide(buffer_id, 2, 2, 1),
18385        ],
18386        None,
18387        &mut cx,
18388    );
18389}
18390
18391#[gpui::test]
18392async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18393    let (buffer_id, mut cx) = setup_indent_guides_editor(
18394        &"
18395        fn main() {
18396            let a = 1;
18397
18398            let c = 3;
18399        }"
18400        .unindent(),
18401        cx,
18402    )
18403    .await;
18404
18405    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18406}
18407
18408#[gpui::test]
18409async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18410    let (buffer_id, mut cx) = setup_indent_guides_editor(
18411        &"
18412        fn main() {
18413            let a = 1;
18414
18415            let c = 3;
18416
18417            if a == 3 {
18418                let b = 2;
18419            } else {
18420                let c = 3;
18421            }
18422        }"
18423        .unindent(),
18424        cx,
18425    )
18426    .await;
18427
18428    assert_indent_guides(
18429        0..11,
18430        vec![
18431            indent_guide(buffer_id, 1, 9, 0),
18432            indent_guide(buffer_id, 6, 6, 1),
18433            indent_guide(buffer_id, 8, 8, 1),
18434        ],
18435        None,
18436        &mut cx,
18437    );
18438}
18439
18440#[gpui::test]
18441async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18442    let (buffer_id, mut cx) = setup_indent_guides_editor(
18443        &"
18444        fn main() {
18445            let a = 1;
18446
18447            let c = 3;
18448
18449            if a == 3 {
18450                let b = 2;
18451            } else {
18452                let c = 3;
18453            }
18454        }"
18455        .unindent(),
18456        cx,
18457    )
18458    .await;
18459
18460    assert_indent_guides(
18461        1..11,
18462        vec![
18463            indent_guide(buffer_id, 1, 9, 0),
18464            indent_guide(buffer_id, 6, 6, 1),
18465            indent_guide(buffer_id, 8, 8, 1),
18466        ],
18467        None,
18468        &mut cx,
18469    );
18470}
18471
18472#[gpui::test]
18473async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18474    let (buffer_id, mut cx) = setup_indent_guides_editor(
18475        &"
18476        fn main() {
18477            let a = 1;
18478
18479            let c = 3;
18480
18481            if a == 3 {
18482                let b = 2;
18483            } else {
18484                let c = 3;
18485            }
18486        }"
18487        .unindent(),
18488        cx,
18489    )
18490    .await;
18491
18492    assert_indent_guides(
18493        1..10,
18494        vec![
18495            indent_guide(buffer_id, 1, 9, 0),
18496            indent_guide(buffer_id, 6, 6, 1),
18497            indent_guide(buffer_id, 8, 8, 1),
18498        ],
18499        None,
18500        &mut cx,
18501    );
18502}
18503
18504#[gpui::test]
18505async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18506    let (buffer_id, mut cx) = setup_indent_guides_editor(
18507        &"
18508        fn main() {
18509            if a {
18510                b(
18511                    c,
18512                    d,
18513                )
18514            } else {
18515                e(
18516                    f
18517                )
18518            }
18519        }"
18520        .unindent(),
18521        cx,
18522    )
18523    .await;
18524
18525    assert_indent_guides(
18526        0..11,
18527        vec![
18528            indent_guide(buffer_id, 1, 10, 0),
18529            indent_guide(buffer_id, 2, 5, 1),
18530            indent_guide(buffer_id, 7, 9, 1),
18531            indent_guide(buffer_id, 3, 4, 2),
18532            indent_guide(buffer_id, 8, 8, 2),
18533        ],
18534        None,
18535        &mut cx,
18536    );
18537
18538    cx.update_editor(|editor, window, cx| {
18539        editor.fold_at(MultiBufferRow(2), window, cx);
18540        assert_eq!(
18541            editor.display_text(cx),
18542            "
18543            fn main() {
18544                if a {
18545                    b(⋯
18546                    )
18547                } else {
18548                    e(
18549                        f
18550                    )
18551                }
18552            }"
18553            .unindent()
18554        );
18555    });
18556
18557    assert_indent_guides(
18558        0..11,
18559        vec![
18560            indent_guide(buffer_id, 1, 10, 0),
18561            indent_guide(buffer_id, 2, 5, 1),
18562            indent_guide(buffer_id, 7, 9, 1),
18563            indent_guide(buffer_id, 8, 8, 2),
18564        ],
18565        None,
18566        &mut cx,
18567    );
18568}
18569
18570#[gpui::test]
18571async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18572    let (buffer_id, mut cx) = setup_indent_guides_editor(
18573        &"
18574        block1
18575            block2
18576                block3
18577                    block4
18578            block2
18579        block1
18580        block1"
18581            .unindent(),
18582        cx,
18583    )
18584    .await;
18585
18586    assert_indent_guides(
18587        1..10,
18588        vec![
18589            indent_guide(buffer_id, 1, 4, 0),
18590            indent_guide(buffer_id, 2, 3, 1),
18591            indent_guide(buffer_id, 3, 3, 2),
18592        ],
18593        None,
18594        &mut cx,
18595    );
18596}
18597
18598#[gpui::test]
18599async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18600    let (buffer_id, mut cx) = setup_indent_guides_editor(
18601        &"
18602        block1
18603            block2
18604                block3
18605
18606        block1
18607        block1"
18608            .unindent(),
18609        cx,
18610    )
18611    .await;
18612
18613    assert_indent_guides(
18614        0..6,
18615        vec![
18616            indent_guide(buffer_id, 1, 2, 0),
18617            indent_guide(buffer_id, 2, 2, 1),
18618        ],
18619        None,
18620        &mut cx,
18621    );
18622}
18623
18624#[gpui::test]
18625async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18626    let (buffer_id, mut cx) = setup_indent_guides_editor(
18627        &"
18628        function component() {
18629        \treturn (
18630        \t\t\t
18631        \t\t<div>
18632        \t\t\t<abc></abc>
18633        \t\t</div>
18634        \t)
18635        }"
18636        .unindent(),
18637        cx,
18638    )
18639    .await;
18640
18641    assert_indent_guides(
18642        0..8,
18643        vec![
18644            indent_guide(buffer_id, 1, 6, 0),
18645            indent_guide(buffer_id, 2, 5, 1),
18646            indent_guide(buffer_id, 4, 4, 2),
18647        ],
18648        None,
18649        &mut cx,
18650    );
18651}
18652
18653#[gpui::test]
18654async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18655    let (buffer_id, mut cx) = setup_indent_guides_editor(
18656        &"
18657        function component() {
18658        \treturn (
18659        \t
18660        \t\t<div>
18661        \t\t\t<abc></abc>
18662        \t\t</div>
18663        \t)
18664        }"
18665        .unindent(),
18666        cx,
18667    )
18668    .await;
18669
18670    assert_indent_guides(
18671        0..8,
18672        vec![
18673            indent_guide(buffer_id, 1, 6, 0),
18674            indent_guide(buffer_id, 2, 5, 1),
18675            indent_guide(buffer_id, 4, 4, 2),
18676        ],
18677        None,
18678        &mut cx,
18679    );
18680}
18681
18682#[gpui::test]
18683async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18684    let (buffer_id, mut cx) = setup_indent_guides_editor(
18685        &"
18686        block1
18687
18688
18689
18690            block2
18691        "
18692        .unindent(),
18693        cx,
18694    )
18695    .await;
18696
18697    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18698}
18699
18700#[gpui::test]
18701async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18702    let (buffer_id, mut cx) = setup_indent_guides_editor(
18703        &"
18704        def a:
18705        \tb = 3
18706        \tif True:
18707        \t\tc = 4
18708        \t\td = 5
18709        \tprint(b)
18710        "
18711        .unindent(),
18712        cx,
18713    )
18714    .await;
18715
18716    assert_indent_guides(
18717        0..6,
18718        vec![
18719            indent_guide(buffer_id, 1, 5, 0),
18720            indent_guide(buffer_id, 3, 4, 1),
18721        ],
18722        None,
18723        &mut cx,
18724    );
18725}
18726
18727#[gpui::test]
18728async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18729    let (buffer_id, mut cx) = setup_indent_guides_editor(
18730        &"
18731    fn main() {
18732        let a = 1;
18733    }"
18734        .unindent(),
18735        cx,
18736    )
18737    .await;
18738
18739    cx.update_editor(|editor, window, cx| {
18740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18741            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18742        });
18743    });
18744
18745    assert_indent_guides(
18746        0..3,
18747        vec![indent_guide(buffer_id, 1, 1, 0)],
18748        Some(vec![0]),
18749        &mut cx,
18750    );
18751}
18752
18753#[gpui::test]
18754async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18755    let (buffer_id, mut cx) = setup_indent_guides_editor(
18756        &"
18757    fn main() {
18758        if 1 == 2 {
18759            let a = 1;
18760        }
18761    }"
18762        .unindent(),
18763        cx,
18764    )
18765    .await;
18766
18767    cx.update_editor(|editor, window, cx| {
18768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18769            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18770        });
18771    });
18772
18773    assert_indent_guides(
18774        0..4,
18775        vec![
18776            indent_guide(buffer_id, 1, 3, 0),
18777            indent_guide(buffer_id, 2, 2, 1),
18778        ],
18779        Some(vec![1]),
18780        &mut cx,
18781    );
18782
18783    cx.update_editor(|editor, window, cx| {
18784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18785            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18786        });
18787    });
18788
18789    assert_indent_guides(
18790        0..4,
18791        vec![
18792            indent_guide(buffer_id, 1, 3, 0),
18793            indent_guide(buffer_id, 2, 2, 1),
18794        ],
18795        Some(vec![1]),
18796        &mut cx,
18797    );
18798
18799    cx.update_editor(|editor, window, cx| {
18800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18801            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18802        });
18803    });
18804
18805    assert_indent_guides(
18806        0..4,
18807        vec![
18808            indent_guide(buffer_id, 1, 3, 0),
18809            indent_guide(buffer_id, 2, 2, 1),
18810        ],
18811        Some(vec![0]),
18812        &mut cx,
18813    );
18814}
18815
18816#[gpui::test]
18817async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18818    let (buffer_id, mut cx) = setup_indent_guides_editor(
18819        &"
18820    fn main() {
18821        let a = 1;
18822
18823        let b = 2;
18824    }"
18825        .unindent(),
18826        cx,
18827    )
18828    .await;
18829
18830    cx.update_editor(|editor, window, cx| {
18831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18832            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18833        });
18834    });
18835
18836    assert_indent_guides(
18837        0..5,
18838        vec![indent_guide(buffer_id, 1, 3, 0)],
18839        Some(vec![0]),
18840        &mut cx,
18841    );
18842}
18843
18844#[gpui::test]
18845async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18846    let (buffer_id, mut cx) = setup_indent_guides_editor(
18847        &"
18848    def m:
18849        a = 1
18850        pass"
18851            .unindent(),
18852        cx,
18853    )
18854    .await;
18855
18856    cx.update_editor(|editor, window, cx| {
18857        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18858            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18859        });
18860    });
18861
18862    assert_indent_guides(
18863        0..3,
18864        vec![indent_guide(buffer_id, 1, 2, 0)],
18865        Some(vec![0]),
18866        &mut cx,
18867    );
18868}
18869
18870#[gpui::test]
18871async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18872    init_test(cx, |_| {});
18873    let mut cx = EditorTestContext::new(cx).await;
18874    let text = indoc! {
18875        "
18876        impl A {
18877            fn b() {
18878                0;
18879                3;
18880                5;
18881                6;
18882                7;
18883            }
18884        }
18885        "
18886    };
18887    let base_text = indoc! {
18888        "
18889        impl A {
18890            fn b() {
18891                0;
18892                1;
18893                2;
18894                3;
18895                4;
18896            }
18897            fn c() {
18898                5;
18899                6;
18900                7;
18901            }
18902        }
18903        "
18904    };
18905
18906    cx.update_editor(|editor, window, cx| {
18907        editor.set_text(text, window, cx);
18908
18909        editor.buffer().update(cx, |multibuffer, cx| {
18910            let buffer = multibuffer.as_singleton().unwrap();
18911            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18912
18913            multibuffer.set_all_diff_hunks_expanded(cx);
18914            multibuffer.add_diff(diff, cx);
18915
18916            buffer.read(cx).remote_id()
18917        })
18918    });
18919    cx.run_until_parked();
18920
18921    cx.assert_state_with_diff(
18922        indoc! { "
18923          impl A {
18924              fn b() {
18925                  0;
18926        -         1;
18927        -         2;
18928                  3;
18929        -         4;
18930        -     }
18931        -     fn c() {
18932                  5;
18933                  6;
18934                  7;
18935              }
18936          }
18937          ˇ"
18938        }
18939        .to_string(),
18940    );
18941
18942    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18943        editor
18944            .snapshot(window, cx)
18945            .buffer_snapshot
18946            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18947            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18948            .collect::<Vec<_>>()
18949    });
18950    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18951    assert_eq!(
18952        actual_guides,
18953        vec![
18954            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18955            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18956            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18957        ]
18958    );
18959}
18960
18961#[gpui::test]
18962async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18963    init_test(cx, |_| {});
18964    let mut cx = EditorTestContext::new(cx).await;
18965
18966    let diff_base = r#"
18967        a
18968        b
18969        c
18970        "#
18971    .unindent();
18972
18973    cx.set_state(
18974        &r#"
18975        ˇA
18976        b
18977        C
18978        "#
18979        .unindent(),
18980    );
18981    cx.set_head_text(&diff_base);
18982    cx.update_editor(|editor, window, cx| {
18983        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18984    });
18985    executor.run_until_parked();
18986
18987    let both_hunks_expanded = r#"
18988        - a
18989        + ˇA
18990          b
18991        - c
18992        + C
18993        "#
18994    .unindent();
18995
18996    cx.assert_state_with_diff(both_hunks_expanded.clone());
18997
18998    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18999        let snapshot = editor.snapshot(window, cx);
19000        let hunks = editor
19001            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19002            .collect::<Vec<_>>();
19003        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19004        let buffer_id = hunks[0].buffer_id;
19005        hunks
19006            .into_iter()
19007            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19008            .collect::<Vec<_>>()
19009    });
19010    assert_eq!(hunk_ranges.len(), 2);
19011
19012    cx.update_editor(|editor, _, cx| {
19013        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19014    });
19015    executor.run_until_parked();
19016
19017    let second_hunk_expanded = r#"
19018          ˇA
19019          b
19020        - c
19021        + C
19022        "#
19023    .unindent();
19024
19025    cx.assert_state_with_diff(second_hunk_expanded);
19026
19027    cx.update_editor(|editor, _, cx| {
19028        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19029    });
19030    executor.run_until_parked();
19031
19032    cx.assert_state_with_diff(both_hunks_expanded.clone());
19033
19034    cx.update_editor(|editor, _, cx| {
19035        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19036    });
19037    executor.run_until_parked();
19038
19039    let first_hunk_expanded = r#"
19040        - a
19041        + ˇA
19042          b
19043          C
19044        "#
19045    .unindent();
19046
19047    cx.assert_state_with_diff(first_hunk_expanded);
19048
19049    cx.update_editor(|editor, _, cx| {
19050        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19051    });
19052    executor.run_until_parked();
19053
19054    cx.assert_state_with_diff(both_hunks_expanded);
19055
19056    cx.set_state(
19057        &r#"
19058        ˇA
19059        b
19060        "#
19061        .unindent(),
19062    );
19063    cx.run_until_parked();
19064
19065    // TODO this cursor position seems bad
19066    cx.assert_state_with_diff(
19067        r#"
19068        - ˇa
19069        + A
19070          b
19071        "#
19072        .unindent(),
19073    );
19074
19075    cx.update_editor(|editor, window, cx| {
19076        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19077    });
19078
19079    cx.assert_state_with_diff(
19080        r#"
19081            - ˇa
19082            + A
19083              b
19084            - c
19085            "#
19086        .unindent(),
19087    );
19088
19089    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19090        let snapshot = editor.snapshot(window, cx);
19091        let hunks = editor
19092            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19093            .collect::<Vec<_>>();
19094        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19095        let buffer_id = hunks[0].buffer_id;
19096        hunks
19097            .into_iter()
19098            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19099            .collect::<Vec<_>>()
19100    });
19101    assert_eq!(hunk_ranges.len(), 2);
19102
19103    cx.update_editor(|editor, _, cx| {
19104        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19105    });
19106    executor.run_until_parked();
19107
19108    cx.assert_state_with_diff(
19109        r#"
19110        - ˇa
19111        + A
19112          b
19113        "#
19114        .unindent(),
19115    );
19116}
19117
19118#[gpui::test]
19119async fn test_toggle_deletion_hunk_at_start_of_file(
19120    executor: BackgroundExecutor,
19121    cx: &mut TestAppContext,
19122) {
19123    init_test(cx, |_| {});
19124    let mut cx = EditorTestContext::new(cx).await;
19125
19126    let diff_base = r#"
19127        a
19128        b
19129        c
19130        "#
19131    .unindent();
19132
19133    cx.set_state(
19134        &r#"
19135        ˇb
19136        c
19137        "#
19138        .unindent(),
19139    );
19140    cx.set_head_text(&diff_base);
19141    cx.update_editor(|editor, window, cx| {
19142        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19143    });
19144    executor.run_until_parked();
19145
19146    let hunk_expanded = r#"
19147        - a
19148          ˇb
19149          c
19150        "#
19151    .unindent();
19152
19153    cx.assert_state_with_diff(hunk_expanded.clone());
19154
19155    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19156        let snapshot = editor.snapshot(window, cx);
19157        let hunks = editor
19158            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19159            .collect::<Vec<_>>();
19160        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19161        let buffer_id = hunks[0].buffer_id;
19162        hunks
19163            .into_iter()
19164            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19165            .collect::<Vec<_>>()
19166    });
19167    assert_eq!(hunk_ranges.len(), 1);
19168
19169    cx.update_editor(|editor, _, cx| {
19170        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19171    });
19172    executor.run_until_parked();
19173
19174    let hunk_collapsed = r#"
19175          ˇb
19176          c
19177        "#
19178    .unindent();
19179
19180    cx.assert_state_with_diff(hunk_collapsed);
19181
19182    cx.update_editor(|editor, _, cx| {
19183        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19184    });
19185    executor.run_until_parked();
19186
19187    cx.assert_state_with_diff(hunk_expanded.clone());
19188}
19189
19190#[gpui::test]
19191async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19192    init_test(cx, |_| {});
19193
19194    let fs = FakeFs::new(cx.executor());
19195    fs.insert_tree(
19196        path!("/test"),
19197        json!({
19198            ".git": {},
19199            "file-1": "ONE\n",
19200            "file-2": "TWO\n",
19201            "file-3": "THREE\n",
19202        }),
19203    )
19204    .await;
19205
19206    fs.set_head_for_repo(
19207        path!("/test/.git").as_ref(),
19208        &[
19209            ("file-1".into(), "one\n".into()),
19210            ("file-2".into(), "two\n".into()),
19211            ("file-3".into(), "three\n".into()),
19212        ],
19213        "deadbeef",
19214    );
19215
19216    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19217    let mut buffers = vec![];
19218    for i in 1..=3 {
19219        let buffer = project
19220            .update(cx, |project, cx| {
19221                let path = format!(path!("/test/file-{}"), i);
19222                project.open_local_buffer(path, cx)
19223            })
19224            .await
19225            .unwrap();
19226        buffers.push(buffer);
19227    }
19228
19229    let multibuffer = cx.new(|cx| {
19230        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19231        multibuffer.set_all_diff_hunks_expanded(cx);
19232        for buffer in &buffers {
19233            let snapshot = buffer.read(cx).snapshot();
19234            multibuffer.set_excerpts_for_path(
19235                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19236                buffer.clone(),
19237                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19238                DEFAULT_MULTIBUFFER_CONTEXT,
19239                cx,
19240            );
19241        }
19242        multibuffer
19243    });
19244
19245    let editor = cx.add_window(|window, cx| {
19246        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19247    });
19248    cx.run_until_parked();
19249
19250    let snapshot = editor
19251        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19252        .unwrap();
19253    let hunks = snapshot
19254        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19255        .map(|hunk| match hunk {
19256            DisplayDiffHunk::Unfolded {
19257                display_row_range, ..
19258            } => display_row_range,
19259            DisplayDiffHunk::Folded { .. } => unreachable!(),
19260        })
19261        .collect::<Vec<_>>();
19262    assert_eq!(
19263        hunks,
19264        [
19265            DisplayRow(2)..DisplayRow(4),
19266            DisplayRow(7)..DisplayRow(9),
19267            DisplayRow(12)..DisplayRow(14),
19268        ]
19269    );
19270}
19271
19272#[gpui::test]
19273async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19274    init_test(cx, |_| {});
19275
19276    let mut cx = EditorTestContext::new(cx).await;
19277    cx.set_head_text(indoc! { "
19278        one
19279        two
19280        three
19281        four
19282        five
19283        "
19284    });
19285    cx.set_index_text(indoc! { "
19286        one
19287        two
19288        three
19289        four
19290        five
19291        "
19292    });
19293    cx.set_state(indoc! {"
19294        one
19295        TWO
19296        ˇTHREE
19297        FOUR
19298        five
19299    "});
19300    cx.run_until_parked();
19301    cx.update_editor(|editor, window, cx| {
19302        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19303    });
19304    cx.run_until_parked();
19305    cx.assert_index_text(Some(indoc! {"
19306        one
19307        TWO
19308        THREE
19309        FOUR
19310        five
19311    "}));
19312    cx.set_state(indoc! { "
19313        one
19314        TWO
19315        ˇTHREE-HUNDRED
19316        FOUR
19317        five
19318    "});
19319    cx.run_until_parked();
19320    cx.update_editor(|editor, window, cx| {
19321        let snapshot = editor.snapshot(window, cx);
19322        let hunks = editor
19323            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19324            .collect::<Vec<_>>();
19325        assert_eq!(hunks.len(), 1);
19326        assert_eq!(
19327            hunks[0].status(),
19328            DiffHunkStatus {
19329                kind: DiffHunkStatusKind::Modified,
19330                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19331            }
19332        );
19333
19334        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19335    });
19336    cx.run_until_parked();
19337    cx.assert_index_text(Some(indoc! {"
19338        one
19339        TWO
19340        THREE-HUNDRED
19341        FOUR
19342        five
19343    "}));
19344}
19345
19346#[gpui::test]
19347fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19348    init_test(cx, |_| {});
19349
19350    let editor = cx.add_window(|window, cx| {
19351        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19352        build_editor(buffer, window, cx)
19353    });
19354
19355    let render_args = Arc::new(Mutex::new(None));
19356    let snapshot = editor
19357        .update(cx, |editor, window, cx| {
19358            let snapshot = editor.buffer().read(cx).snapshot(cx);
19359            let range =
19360                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19361
19362            struct RenderArgs {
19363                row: MultiBufferRow,
19364                folded: bool,
19365                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19366            }
19367
19368            let crease = Crease::inline(
19369                range,
19370                FoldPlaceholder::test(),
19371                {
19372                    let toggle_callback = render_args.clone();
19373                    move |row, folded, callback, _window, _cx| {
19374                        *toggle_callback.lock() = Some(RenderArgs {
19375                            row,
19376                            folded,
19377                            callback,
19378                        });
19379                        div()
19380                    }
19381                },
19382                |_row, _folded, _window, _cx| div(),
19383            );
19384
19385            editor.insert_creases(Some(crease), cx);
19386            let snapshot = editor.snapshot(window, cx);
19387            let _div = snapshot.render_crease_toggle(
19388                MultiBufferRow(1),
19389                false,
19390                cx.entity().clone(),
19391                window,
19392                cx,
19393            );
19394            snapshot
19395        })
19396        .unwrap();
19397
19398    let render_args = render_args.lock().take().unwrap();
19399    assert_eq!(render_args.row, MultiBufferRow(1));
19400    assert!(!render_args.folded);
19401    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19402
19403    cx.update_window(*editor, |_, window, cx| {
19404        (render_args.callback)(true, window, cx)
19405    })
19406    .unwrap();
19407    let snapshot = editor
19408        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19409        .unwrap();
19410    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19411
19412    cx.update_window(*editor, |_, window, cx| {
19413        (render_args.callback)(false, window, cx)
19414    })
19415    .unwrap();
19416    let snapshot = editor
19417        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19418        .unwrap();
19419    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19420}
19421
19422#[gpui::test]
19423async fn test_input_text(cx: &mut TestAppContext) {
19424    init_test(cx, |_| {});
19425    let mut cx = EditorTestContext::new(cx).await;
19426
19427    cx.set_state(
19428        &r#"ˇone
19429        two
19430
19431        three
19432        fourˇ
19433        five
19434
19435        siˇx"#
19436            .unindent(),
19437    );
19438
19439    cx.dispatch_action(HandleInput(String::new()));
19440    cx.assert_editor_state(
19441        &r#"ˇone
19442        two
19443
19444        three
19445        fourˇ
19446        five
19447
19448        siˇx"#
19449            .unindent(),
19450    );
19451
19452    cx.dispatch_action(HandleInput("AAAA".to_string()));
19453    cx.assert_editor_state(
19454        &r#"AAAAˇone
19455        two
19456
19457        three
19458        fourAAAAˇ
19459        five
19460
19461        siAAAAˇx"#
19462            .unindent(),
19463    );
19464}
19465
19466#[gpui::test]
19467async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19468    init_test(cx, |_| {});
19469
19470    let mut cx = EditorTestContext::new(cx).await;
19471    cx.set_state(
19472        r#"let foo = 1;
19473let foo = 2;
19474let foo = 3;
19475let fooˇ = 4;
19476let foo = 5;
19477let foo = 6;
19478let foo = 7;
19479let foo = 8;
19480let foo = 9;
19481let foo = 10;
19482let foo = 11;
19483let foo = 12;
19484let foo = 13;
19485let foo = 14;
19486let foo = 15;"#,
19487    );
19488
19489    cx.update_editor(|e, window, cx| {
19490        assert_eq!(
19491            e.next_scroll_position,
19492            NextScrollCursorCenterTopBottom::Center,
19493            "Default next scroll direction is center",
19494        );
19495
19496        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19497        assert_eq!(
19498            e.next_scroll_position,
19499            NextScrollCursorCenterTopBottom::Top,
19500            "After center, next scroll direction should be top",
19501        );
19502
19503        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19504        assert_eq!(
19505            e.next_scroll_position,
19506            NextScrollCursorCenterTopBottom::Bottom,
19507            "After top, next scroll direction should be bottom",
19508        );
19509
19510        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19511        assert_eq!(
19512            e.next_scroll_position,
19513            NextScrollCursorCenterTopBottom::Center,
19514            "After bottom, scrolling should start over",
19515        );
19516
19517        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19518        assert_eq!(
19519            e.next_scroll_position,
19520            NextScrollCursorCenterTopBottom::Top,
19521            "Scrolling continues if retriggered fast enough"
19522        );
19523    });
19524
19525    cx.executor()
19526        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19527    cx.executor().run_until_parked();
19528    cx.update_editor(|e, _, _| {
19529        assert_eq!(
19530            e.next_scroll_position,
19531            NextScrollCursorCenterTopBottom::Center,
19532            "If scrolling is not triggered fast enough, it should reset"
19533        );
19534    });
19535}
19536
19537#[gpui::test]
19538async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19539    init_test(cx, |_| {});
19540    let mut cx = EditorLspTestContext::new_rust(
19541        lsp::ServerCapabilities {
19542            definition_provider: Some(lsp::OneOf::Left(true)),
19543            references_provider: Some(lsp::OneOf::Left(true)),
19544            ..lsp::ServerCapabilities::default()
19545        },
19546        cx,
19547    )
19548    .await;
19549
19550    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19551        let go_to_definition = cx
19552            .lsp
19553            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19554                move |params, _| async move {
19555                    if empty_go_to_definition {
19556                        Ok(None)
19557                    } else {
19558                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19559                            uri: params.text_document_position_params.text_document.uri,
19560                            range: lsp::Range::new(
19561                                lsp::Position::new(4, 3),
19562                                lsp::Position::new(4, 6),
19563                            ),
19564                        })))
19565                    }
19566                },
19567            );
19568        let references = cx
19569            .lsp
19570            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19571                Ok(Some(vec![lsp::Location {
19572                    uri: params.text_document_position.text_document.uri,
19573                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19574                }]))
19575            });
19576        (go_to_definition, references)
19577    };
19578
19579    cx.set_state(
19580        &r#"fn one() {
19581            let mut a = ˇtwo();
19582        }
19583
19584        fn two() {}"#
19585            .unindent(),
19586    );
19587    set_up_lsp_handlers(false, &mut cx);
19588    let navigated = cx
19589        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19590        .await
19591        .expect("Failed to navigate to definition");
19592    assert_eq!(
19593        navigated,
19594        Navigated::Yes,
19595        "Should have navigated to definition from the GetDefinition response"
19596    );
19597    cx.assert_editor_state(
19598        &r#"fn one() {
19599            let mut a = two();
19600        }
19601
19602        fn «twoˇ»() {}"#
19603            .unindent(),
19604    );
19605
19606    let editors = cx.update_workspace(|workspace, _, cx| {
19607        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19608    });
19609    cx.update_editor(|_, _, test_editor_cx| {
19610        assert_eq!(
19611            editors.len(),
19612            1,
19613            "Initially, only one, test, editor should be open in the workspace"
19614        );
19615        assert_eq!(
19616            test_editor_cx.entity(),
19617            editors.last().expect("Asserted len is 1").clone()
19618        );
19619    });
19620
19621    set_up_lsp_handlers(true, &mut cx);
19622    let navigated = cx
19623        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19624        .await
19625        .expect("Failed to navigate to lookup references");
19626    assert_eq!(
19627        navigated,
19628        Navigated::Yes,
19629        "Should have navigated to references as a fallback after empty GoToDefinition response"
19630    );
19631    // We should not change the selections in the existing file,
19632    // if opening another milti buffer with the references
19633    cx.assert_editor_state(
19634        &r#"fn one() {
19635            let mut a = two();
19636        }
19637
19638        fn «twoˇ»() {}"#
19639            .unindent(),
19640    );
19641    let editors = cx.update_workspace(|workspace, _, cx| {
19642        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19643    });
19644    cx.update_editor(|_, _, test_editor_cx| {
19645        assert_eq!(
19646            editors.len(),
19647            2,
19648            "After falling back to references search, we open a new editor with the results"
19649        );
19650        let references_fallback_text = editors
19651            .into_iter()
19652            .find(|new_editor| *new_editor != test_editor_cx.entity())
19653            .expect("Should have one non-test editor now")
19654            .read(test_editor_cx)
19655            .text(test_editor_cx);
19656        assert_eq!(
19657            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19658            "Should use the range from the references response and not the GoToDefinition one"
19659        );
19660    });
19661}
19662
19663#[gpui::test]
19664async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19665    init_test(cx, |_| {});
19666    cx.update(|cx| {
19667        let mut editor_settings = EditorSettings::get_global(cx).clone();
19668        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19669        EditorSettings::override_global(editor_settings, cx);
19670    });
19671    let mut cx = EditorLspTestContext::new_rust(
19672        lsp::ServerCapabilities {
19673            definition_provider: Some(lsp::OneOf::Left(true)),
19674            references_provider: Some(lsp::OneOf::Left(true)),
19675            ..lsp::ServerCapabilities::default()
19676        },
19677        cx,
19678    )
19679    .await;
19680    let original_state = r#"fn one() {
19681        let mut a = ˇtwo();
19682    }
19683
19684    fn two() {}"#
19685        .unindent();
19686    cx.set_state(&original_state);
19687
19688    let mut go_to_definition = cx
19689        .lsp
19690        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19691            move |_, _| async move { Ok(None) },
19692        );
19693    let _references = cx
19694        .lsp
19695        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19696            panic!("Should not call for references with no go to definition fallback")
19697        });
19698
19699    let navigated = cx
19700        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19701        .await
19702        .expect("Failed to navigate to lookup references");
19703    go_to_definition
19704        .next()
19705        .await
19706        .expect("Should have called the go_to_definition handler");
19707
19708    assert_eq!(
19709        navigated,
19710        Navigated::No,
19711        "Should have navigated to references as a fallback after empty GoToDefinition response"
19712    );
19713    cx.assert_editor_state(&original_state);
19714    let editors = cx.update_workspace(|workspace, _, cx| {
19715        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19716    });
19717    cx.update_editor(|_, _, _| {
19718        assert_eq!(
19719            editors.len(),
19720            1,
19721            "After unsuccessful fallback, no other editor should have been opened"
19722        );
19723    });
19724}
19725
19726#[gpui::test]
19727async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19728    init_test(cx, |_| {});
19729
19730    let language = Arc::new(Language::new(
19731        LanguageConfig::default(),
19732        Some(tree_sitter_rust::LANGUAGE.into()),
19733    ));
19734
19735    let text = r#"
19736        #[cfg(test)]
19737        mod tests() {
19738            #[test]
19739            fn runnable_1() {
19740                let a = 1;
19741            }
19742
19743            #[test]
19744            fn runnable_2() {
19745                let a = 1;
19746                let b = 2;
19747            }
19748        }
19749    "#
19750    .unindent();
19751
19752    let fs = FakeFs::new(cx.executor());
19753    fs.insert_file("/file.rs", Default::default()).await;
19754
19755    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19756    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19757    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19758    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19759    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19760
19761    let editor = cx.new_window_entity(|window, cx| {
19762        Editor::new(
19763            EditorMode::full(),
19764            multi_buffer,
19765            Some(project.clone()),
19766            window,
19767            cx,
19768        )
19769    });
19770
19771    editor.update_in(cx, |editor, window, cx| {
19772        let snapshot = editor.buffer().read(cx).snapshot(cx);
19773        editor.tasks.insert(
19774            (buffer.read(cx).remote_id(), 3),
19775            RunnableTasks {
19776                templates: vec![],
19777                offset: snapshot.anchor_before(43),
19778                column: 0,
19779                extra_variables: HashMap::default(),
19780                context_range: BufferOffset(43)..BufferOffset(85),
19781            },
19782        );
19783        editor.tasks.insert(
19784            (buffer.read(cx).remote_id(), 8),
19785            RunnableTasks {
19786                templates: vec![],
19787                offset: snapshot.anchor_before(86),
19788                column: 0,
19789                extra_variables: HashMap::default(),
19790                context_range: BufferOffset(86)..BufferOffset(191),
19791            },
19792        );
19793
19794        // Test finding task when cursor is inside function body
19795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19796            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19797        });
19798        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19799        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19800
19801        // Test finding task when cursor is on function name
19802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19803            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19804        });
19805        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19806        assert_eq!(row, 8, "Should find task when cursor is on function name");
19807    });
19808}
19809
19810#[gpui::test]
19811async fn test_folding_buffers(cx: &mut TestAppContext) {
19812    init_test(cx, |_| {});
19813
19814    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19815    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19816    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19817
19818    let fs = FakeFs::new(cx.executor());
19819    fs.insert_tree(
19820        path!("/a"),
19821        json!({
19822            "first.rs": sample_text_1,
19823            "second.rs": sample_text_2,
19824            "third.rs": sample_text_3,
19825        }),
19826    )
19827    .await;
19828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19830    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19831    let worktree = project.update(cx, |project, cx| {
19832        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19833        assert_eq!(worktrees.len(), 1);
19834        worktrees.pop().unwrap()
19835    });
19836    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19837
19838    let buffer_1 = project
19839        .update(cx, |project, cx| {
19840            project.open_buffer((worktree_id, "first.rs"), cx)
19841        })
19842        .await
19843        .unwrap();
19844    let buffer_2 = project
19845        .update(cx, |project, cx| {
19846            project.open_buffer((worktree_id, "second.rs"), cx)
19847        })
19848        .await
19849        .unwrap();
19850    let buffer_3 = project
19851        .update(cx, |project, cx| {
19852            project.open_buffer((worktree_id, "third.rs"), cx)
19853        })
19854        .await
19855        .unwrap();
19856
19857    let multi_buffer = cx.new(|cx| {
19858        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19859        multi_buffer.push_excerpts(
19860            buffer_1.clone(),
19861            [
19862                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19863                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19864                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19865            ],
19866            cx,
19867        );
19868        multi_buffer.push_excerpts(
19869            buffer_2.clone(),
19870            [
19871                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19872                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19873                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19874            ],
19875            cx,
19876        );
19877        multi_buffer.push_excerpts(
19878            buffer_3.clone(),
19879            [
19880                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19881                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19882                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19883            ],
19884            cx,
19885        );
19886        multi_buffer
19887    });
19888    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19889        Editor::new(
19890            EditorMode::full(),
19891            multi_buffer.clone(),
19892            Some(project.clone()),
19893            window,
19894            cx,
19895        )
19896    });
19897
19898    assert_eq!(
19899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19900        "\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",
19901    );
19902
19903    multi_buffer_editor.update(cx, |editor, cx| {
19904        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19905    });
19906    assert_eq!(
19907        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19908        "\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",
19909        "After folding the first buffer, its text should not be displayed"
19910    );
19911
19912    multi_buffer_editor.update(cx, |editor, cx| {
19913        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19914    });
19915    assert_eq!(
19916        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19917        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19918        "After folding the second buffer, its text should not be displayed"
19919    );
19920
19921    multi_buffer_editor.update(cx, |editor, cx| {
19922        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19923    });
19924    assert_eq!(
19925        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19926        "\n\n\n\n\n",
19927        "After folding the third buffer, its text should not be displayed"
19928    );
19929
19930    // Emulate selection inside the fold logic, that should work
19931    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19932        editor
19933            .snapshot(window, cx)
19934            .next_line_boundary(Point::new(0, 4));
19935    });
19936
19937    multi_buffer_editor.update(cx, |editor, cx| {
19938        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19939    });
19940    assert_eq!(
19941        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19942        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19943        "After unfolding the second buffer, its text should be displayed"
19944    );
19945
19946    // Typing inside of buffer 1 causes that buffer to be unfolded.
19947    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19948        assert_eq!(
19949            multi_buffer
19950                .read(cx)
19951                .snapshot(cx)
19952                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19953                .collect::<String>(),
19954            "bbbb"
19955        );
19956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19957            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19958        });
19959        editor.handle_input("B", window, cx);
19960    });
19961
19962    assert_eq!(
19963        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19964        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19965        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19966    );
19967
19968    multi_buffer_editor.update(cx, |editor, cx| {
19969        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19970    });
19971    assert_eq!(
19972        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19973        "\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",
19974        "After unfolding the all buffers, all original text should be displayed"
19975    );
19976}
19977
19978#[gpui::test]
19979async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19980    init_test(cx, |_| {});
19981
19982    let sample_text_1 = "1111\n2222\n3333".to_string();
19983    let sample_text_2 = "4444\n5555\n6666".to_string();
19984    let sample_text_3 = "7777\n8888\n9999".to_string();
19985
19986    let fs = FakeFs::new(cx.executor());
19987    fs.insert_tree(
19988        path!("/a"),
19989        json!({
19990            "first.rs": sample_text_1,
19991            "second.rs": sample_text_2,
19992            "third.rs": sample_text_3,
19993        }),
19994    )
19995    .await;
19996    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19997    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19998    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19999    let worktree = project.update(cx, |project, cx| {
20000        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20001        assert_eq!(worktrees.len(), 1);
20002        worktrees.pop().unwrap()
20003    });
20004    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20005
20006    let buffer_1 = project
20007        .update(cx, |project, cx| {
20008            project.open_buffer((worktree_id, "first.rs"), cx)
20009        })
20010        .await
20011        .unwrap();
20012    let buffer_2 = project
20013        .update(cx, |project, cx| {
20014            project.open_buffer((worktree_id, "second.rs"), cx)
20015        })
20016        .await
20017        .unwrap();
20018    let buffer_3 = project
20019        .update(cx, |project, cx| {
20020            project.open_buffer((worktree_id, "third.rs"), cx)
20021        })
20022        .await
20023        .unwrap();
20024
20025    let multi_buffer = cx.new(|cx| {
20026        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20027        multi_buffer.push_excerpts(
20028            buffer_1.clone(),
20029            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20030            cx,
20031        );
20032        multi_buffer.push_excerpts(
20033            buffer_2.clone(),
20034            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20035            cx,
20036        );
20037        multi_buffer.push_excerpts(
20038            buffer_3.clone(),
20039            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20040            cx,
20041        );
20042        multi_buffer
20043    });
20044
20045    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20046        Editor::new(
20047            EditorMode::full(),
20048            multi_buffer,
20049            Some(project.clone()),
20050            window,
20051            cx,
20052        )
20053    });
20054
20055    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20056    assert_eq!(
20057        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20058        full_text,
20059    );
20060
20061    multi_buffer_editor.update(cx, |editor, cx| {
20062        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20063    });
20064    assert_eq!(
20065        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20066        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20067        "After folding the first buffer, its text should not be displayed"
20068    );
20069
20070    multi_buffer_editor.update(cx, |editor, cx| {
20071        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20072    });
20073
20074    assert_eq!(
20075        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20076        "\n\n\n\n\n\n7777\n8888\n9999",
20077        "After folding the second buffer, its text should not be displayed"
20078    );
20079
20080    multi_buffer_editor.update(cx, |editor, cx| {
20081        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20082    });
20083    assert_eq!(
20084        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20085        "\n\n\n\n\n",
20086        "After folding the third buffer, its text should not be displayed"
20087    );
20088
20089    multi_buffer_editor.update(cx, |editor, cx| {
20090        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20091    });
20092    assert_eq!(
20093        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20094        "\n\n\n\n4444\n5555\n6666\n\n",
20095        "After unfolding the second buffer, its text should be displayed"
20096    );
20097
20098    multi_buffer_editor.update(cx, |editor, cx| {
20099        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20100    });
20101    assert_eq!(
20102        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20103        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20104        "After unfolding the first buffer, its text should be displayed"
20105    );
20106
20107    multi_buffer_editor.update(cx, |editor, cx| {
20108        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20109    });
20110    assert_eq!(
20111        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20112        full_text,
20113        "After unfolding all buffers, all original text should be displayed"
20114    );
20115}
20116
20117#[gpui::test]
20118async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20119    init_test(cx, |_| {});
20120
20121    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20122
20123    let fs = FakeFs::new(cx.executor());
20124    fs.insert_tree(
20125        path!("/a"),
20126        json!({
20127            "main.rs": sample_text,
20128        }),
20129    )
20130    .await;
20131    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20132    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20133    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20134    let worktree = project.update(cx, |project, cx| {
20135        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20136        assert_eq!(worktrees.len(), 1);
20137        worktrees.pop().unwrap()
20138    });
20139    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20140
20141    let buffer_1 = project
20142        .update(cx, |project, cx| {
20143            project.open_buffer((worktree_id, "main.rs"), cx)
20144        })
20145        .await
20146        .unwrap();
20147
20148    let multi_buffer = cx.new(|cx| {
20149        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20150        multi_buffer.push_excerpts(
20151            buffer_1.clone(),
20152            [ExcerptRange::new(
20153                Point::new(0, 0)
20154                    ..Point::new(
20155                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20156                        0,
20157                    ),
20158            )],
20159            cx,
20160        );
20161        multi_buffer
20162    });
20163    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20164        Editor::new(
20165            EditorMode::full(),
20166            multi_buffer,
20167            Some(project.clone()),
20168            window,
20169            cx,
20170        )
20171    });
20172
20173    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20174    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20175        enum TestHighlight {}
20176        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20177        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20178        editor.highlight_text::<TestHighlight>(
20179            vec![highlight_range.clone()],
20180            HighlightStyle::color(Hsla::green()),
20181            cx,
20182        );
20183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20184            s.select_ranges(Some(highlight_range))
20185        });
20186    });
20187
20188    let full_text = format!("\n\n{sample_text}");
20189    assert_eq!(
20190        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20191        full_text,
20192    );
20193}
20194
20195#[gpui::test]
20196async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20197    init_test(cx, |_| {});
20198    cx.update(|cx| {
20199        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20200            "keymaps/default-linux.json",
20201            cx,
20202        )
20203        .unwrap();
20204        cx.bind_keys(default_key_bindings);
20205    });
20206
20207    let (editor, cx) = cx.add_window_view(|window, cx| {
20208        let multi_buffer = MultiBuffer::build_multi(
20209            [
20210                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20211                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20212                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20213                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20214            ],
20215            cx,
20216        );
20217        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20218
20219        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20220        // fold all but the second buffer, so that we test navigating between two
20221        // adjacent folded buffers, as well as folded buffers at the start and
20222        // end the multibuffer
20223        editor.fold_buffer(buffer_ids[0], cx);
20224        editor.fold_buffer(buffer_ids[2], cx);
20225        editor.fold_buffer(buffer_ids[3], cx);
20226
20227        editor
20228    });
20229    cx.simulate_resize(size(px(1000.), px(1000.)));
20230
20231    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20232    cx.assert_excerpts_with_selections(indoc! {"
20233        [EXCERPT]
20234        ˇ[FOLDED]
20235        [EXCERPT]
20236        a1
20237        b1
20238        [EXCERPT]
20239        [FOLDED]
20240        [EXCERPT]
20241        [FOLDED]
20242        "
20243    });
20244    cx.simulate_keystroke("down");
20245    cx.assert_excerpts_with_selections(indoc! {"
20246        [EXCERPT]
20247        [FOLDED]
20248        [EXCERPT]
20249        ˇa1
20250        b1
20251        [EXCERPT]
20252        [FOLDED]
20253        [EXCERPT]
20254        [FOLDED]
20255        "
20256    });
20257    cx.simulate_keystroke("down");
20258    cx.assert_excerpts_with_selections(indoc! {"
20259        [EXCERPT]
20260        [FOLDED]
20261        [EXCERPT]
20262        a1
20263        ˇb1
20264        [EXCERPT]
20265        [FOLDED]
20266        [EXCERPT]
20267        [FOLDED]
20268        "
20269    });
20270    cx.simulate_keystroke("down");
20271    cx.assert_excerpts_with_selections(indoc! {"
20272        [EXCERPT]
20273        [FOLDED]
20274        [EXCERPT]
20275        a1
20276        b1
20277        ˇ[EXCERPT]
20278        [FOLDED]
20279        [EXCERPT]
20280        [FOLDED]
20281        "
20282    });
20283    cx.simulate_keystroke("down");
20284    cx.assert_excerpts_with_selections(indoc! {"
20285        [EXCERPT]
20286        [FOLDED]
20287        [EXCERPT]
20288        a1
20289        b1
20290        [EXCERPT]
20291        ˇ[FOLDED]
20292        [EXCERPT]
20293        [FOLDED]
20294        "
20295    });
20296    for _ in 0..5 {
20297        cx.simulate_keystroke("down");
20298        cx.assert_excerpts_with_selections(indoc! {"
20299            [EXCERPT]
20300            [FOLDED]
20301            [EXCERPT]
20302            a1
20303            b1
20304            [EXCERPT]
20305            [FOLDED]
20306            [EXCERPT]
20307            ˇ[FOLDED]
20308            "
20309        });
20310    }
20311
20312    cx.simulate_keystroke("up");
20313    cx.assert_excerpts_with_selections(indoc! {"
20314        [EXCERPT]
20315        [FOLDED]
20316        [EXCERPT]
20317        a1
20318        b1
20319        [EXCERPT]
20320        ˇ[FOLDED]
20321        [EXCERPT]
20322        [FOLDED]
20323        "
20324    });
20325    cx.simulate_keystroke("up");
20326    cx.assert_excerpts_with_selections(indoc! {"
20327        [EXCERPT]
20328        [FOLDED]
20329        [EXCERPT]
20330        a1
20331        b1
20332        ˇ[EXCERPT]
20333        [FOLDED]
20334        [EXCERPT]
20335        [FOLDED]
20336        "
20337    });
20338    cx.simulate_keystroke("up");
20339    cx.assert_excerpts_with_selections(indoc! {"
20340        [EXCERPT]
20341        [FOLDED]
20342        [EXCERPT]
20343        a1
20344        ˇb1
20345        [EXCERPT]
20346        [FOLDED]
20347        [EXCERPT]
20348        [FOLDED]
20349        "
20350    });
20351    cx.simulate_keystroke("up");
20352    cx.assert_excerpts_with_selections(indoc! {"
20353        [EXCERPT]
20354        [FOLDED]
20355        [EXCERPT]
20356        ˇa1
20357        b1
20358        [EXCERPT]
20359        [FOLDED]
20360        [EXCERPT]
20361        [FOLDED]
20362        "
20363    });
20364    for _ in 0..5 {
20365        cx.simulate_keystroke("up");
20366        cx.assert_excerpts_with_selections(indoc! {"
20367            [EXCERPT]
20368            ˇ[FOLDED]
20369            [EXCERPT]
20370            a1
20371            b1
20372            [EXCERPT]
20373            [FOLDED]
20374            [EXCERPT]
20375            [FOLDED]
20376            "
20377        });
20378    }
20379}
20380
20381#[gpui::test]
20382async fn test_inline_completion_text(cx: &mut TestAppContext) {
20383    init_test(cx, |_| {});
20384
20385    // Simple insertion
20386    assert_highlighted_edits(
20387        "Hello, world!",
20388        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20389        true,
20390        cx,
20391        |highlighted_edits, cx| {
20392            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20393            assert_eq!(highlighted_edits.highlights.len(), 1);
20394            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20395            assert_eq!(
20396                highlighted_edits.highlights[0].1.background_color,
20397                Some(cx.theme().status().created_background)
20398            );
20399        },
20400    )
20401    .await;
20402
20403    // Replacement
20404    assert_highlighted_edits(
20405        "This is a test.",
20406        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20407        false,
20408        cx,
20409        |highlighted_edits, cx| {
20410            assert_eq!(highlighted_edits.text, "That is a test.");
20411            assert_eq!(highlighted_edits.highlights.len(), 1);
20412            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20413            assert_eq!(
20414                highlighted_edits.highlights[0].1.background_color,
20415                Some(cx.theme().status().created_background)
20416            );
20417        },
20418    )
20419    .await;
20420
20421    // Multiple edits
20422    assert_highlighted_edits(
20423        "Hello, world!",
20424        vec![
20425            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20426            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20427        ],
20428        false,
20429        cx,
20430        |highlighted_edits, cx| {
20431            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20432            assert_eq!(highlighted_edits.highlights.len(), 2);
20433            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20434            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20435            assert_eq!(
20436                highlighted_edits.highlights[0].1.background_color,
20437                Some(cx.theme().status().created_background)
20438            );
20439            assert_eq!(
20440                highlighted_edits.highlights[1].1.background_color,
20441                Some(cx.theme().status().created_background)
20442            );
20443        },
20444    )
20445    .await;
20446
20447    // Multiple lines with edits
20448    assert_highlighted_edits(
20449        "First line\nSecond line\nThird line\nFourth line",
20450        vec![
20451            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20452            (
20453                Point::new(2, 0)..Point::new(2, 10),
20454                "New third line".to_string(),
20455            ),
20456            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20457        ],
20458        false,
20459        cx,
20460        |highlighted_edits, cx| {
20461            assert_eq!(
20462                highlighted_edits.text,
20463                "Second modified\nNew third line\nFourth updated line"
20464            );
20465            assert_eq!(highlighted_edits.highlights.len(), 3);
20466            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20467            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20468            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20469            for highlight in &highlighted_edits.highlights {
20470                assert_eq!(
20471                    highlight.1.background_color,
20472                    Some(cx.theme().status().created_background)
20473                );
20474            }
20475        },
20476    )
20477    .await;
20478}
20479
20480#[gpui::test]
20481async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20482    init_test(cx, |_| {});
20483
20484    // Deletion
20485    assert_highlighted_edits(
20486        "Hello, world!",
20487        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20488        true,
20489        cx,
20490        |highlighted_edits, cx| {
20491            assert_eq!(highlighted_edits.text, "Hello, world!");
20492            assert_eq!(highlighted_edits.highlights.len(), 1);
20493            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20494            assert_eq!(
20495                highlighted_edits.highlights[0].1.background_color,
20496                Some(cx.theme().status().deleted_background)
20497            );
20498        },
20499    )
20500    .await;
20501
20502    // Insertion
20503    assert_highlighted_edits(
20504        "Hello, world!",
20505        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20506        true,
20507        cx,
20508        |highlighted_edits, cx| {
20509            assert_eq!(highlighted_edits.highlights.len(), 1);
20510            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20511            assert_eq!(
20512                highlighted_edits.highlights[0].1.background_color,
20513                Some(cx.theme().status().created_background)
20514            );
20515        },
20516    )
20517    .await;
20518}
20519
20520async fn assert_highlighted_edits(
20521    text: &str,
20522    edits: Vec<(Range<Point>, String)>,
20523    include_deletions: bool,
20524    cx: &mut TestAppContext,
20525    assertion_fn: impl Fn(HighlightedText, &App),
20526) {
20527    let window = cx.add_window(|window, cx| {
20528        let buffer = MultiBuffer::build_simple(text, cx);
20529        Editor::new(EditorMode::full(), buffer, None, window, cx)
20530    });
20531    let cx = &mut VisualTestContext::from_window(*window, cx);
20532
20533    let (buffer, snapshot) = window
20534        .update(cx, |editor, _window, cx| {
20535            (
20536                editor.buffer().clone(),
20537                editor.buffer().read(cx).snapshot(cx),
20538            )
20539        })
20540        .unwrap();
20541
20542    let edits = edits
20543        .into_iter()
20544        .map(|(range, edit)| {
20545            (
20546                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20547                edit,
20548            )
20549        })
20550        .collect::<Vec<_>>();
20551
20552    let text_anchor_edits = edits
20553        .clone()
20554        .into_iter()
20555        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20556        .collect::<Vec<_>>();
20557
20558    let edit_preview = window
20559        .update(cx, |_, _window, cx| {
20560            buffer
20561                .read(cx)
20562                .as_singleton()
20563                .unwrap()
20564                .read(cx)
20565                .preview_edits(text_anchor_edits.into(), cx)
20566        })
20567        .unwrap()
20568        .await;
20569
20570    cx.update(|_window, cx| {
20571        let highlighted_edits = inline_completion_edit_text(
20572            &snapshot.as_singleton().unwrap().2,
20573            &edits,
20574            &edit_preview,
20575            include_deletions,
20576            cx,
20577        );
20578        assertion_fn(highlighted_edits, cx)
20579    });
20580}
20581
20582#[track_caller]
20583fn assert_breakpoint(
20584    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20585    path: &Arc<Path>,
20586    expected: Vec<(u32, Breakpoint)>,
20587) {
20588    if expected.len() == 0usize {
20589        assert!(!breakpoints.contains_key(path), "{}", path.display());
20590    } else {
20591        let mut breakpoint = breakpoints
20592            .get(path)
20593            .unwrap()
20594            .into_iter()
20595            .map(|breakpoint| {
20596                (
20597                    breakpoint.row,
20598                    Breakpoint {
20599                        message: breakpoint.message.clone(),
20600                        state: breakpoint.state,
20601                        condition: breakpoint.condition.clone(),
20602                        hit_condition: breakpoint.hit_condition.clone(),
20603                    },
20604                )
20605            })
20606            .collect::<Vec<_>>();
20607
20608        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20609
20610        assert_eq!(expected, breakpoint);
20611    }
20612}
20613
20614fn add_log_breakpoint_at_cursor(
20615    editor: &mut Editor,
20616    log_message: &str,
20617    window: &mut Window,
20618    cx: &mut Context<Editor>,
20619) {
20620    let (anchor, bp) = editor
20621        .breakpoints_at_cursors(window, cx)
20622        .first()
20623        .and_then(|(anchor, bp)| {
20624            if let Some(bp) = bp {
20625                Some((*anchor, bp.clone()))
20626            } else {
20627                None
20628            }
20629        })
20630        .unwrap_or_else(|| {
20631            let cursor_position: Point = editor.selections.newest(cx).head();
20632
20633            let breakpoint_position = editor
20634                .snapshot(window, cx)
20635                .display_snapshot
20636                .buffer_snapshot
20637                .anchor_before(Point::new(cursor_position.row, 0));
20638
20639            (breakpoint_position, Breakpoint::new_log(&log_message))
20640        });
20641
20642    editor.edit_breakpoint_at_anchor(
20643        anchor,
20644        bp,
20645        BreakpointEditAction::EditLogMessage(log_message.into()),
20646        cx,
20647    );
20648}
20649
20650#[gpui::test]
20651async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20652    init_test(cx, |_| {});
20653
20654    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20655    let fs = FakeFs::new(cx.executor());
20656    fs.insert_tree(
20657        path!("/a"),
20658        json!({
20659            "main.rs": sample_text,
20660        }),
20661    )
20662    .await;
20663    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20664    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20665    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20666
20667    let fs = FakeFs::new(cx.executor());
20668    fs.insert_tree(
20669        path!("/a"),
20670        json!({
20671            "main.rs": sample_text,
20672        }),
20673    )
20674    .await;
20675    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20676    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20677    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20678    let worktree_id = workspace
20679        .update(cx, |workspace, _window, cx| {
20680            workspace.project().update(cx, |project, cx| {
20681                project.worktrees(cx).next().unwrap().read(cx).id()
20682            })
20683        })
20684        .unwrap();
20685
20686    let buffer = project
20687        .update(cx, |project, cx| {
20688            project.open_buffer((worktree_id, "main.rs"), cx)
20689        })
20690        .await
20691        .unwrap();
20692
20693    let (editor, cx) = cx.add_window_view(|window, cx| {
20694        Editor::new(
20695            EditorMode::full(),
20696            MultiBuffer::build_from_buffer(buffer, cx),
20697            Some(project.clone()),
20698            window,
20699            cx,
20700        )
20701    });
20702
20703    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20704    let abs_path = project.read_with(cx, |project, cx| {
20705        project
20706            .absolute_path(&project_path, cx)
20707            .map(|path_buf| Arc::from(path_buf.to_owned()))
20708            .unwrap()
20709    });
20710
20711    // assert we can add breakpoint on the first line
20712    editor.update_in(cx, |editor, window, cx| {
20713        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20714        editor.move_to_end(&MoveToEnd, window, cx);
20715        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20716    });
20717
20718    let breakpoints = editor.update(cx, |editor, cx| {
20719        editor
20720            .breakpoint_store()
20721            .as_ref()
20722            .unwrap()
20723            .read(cx)
20724            .all_source_breakpoints(cx)
20725            .clone()
20726    });
20727
20728    assert_eq!(1, breakpoints.len());
20729    assert_breakpoint(
20730        &breakpoints,
20731        &abs_path,
20732        vec![
20733            (0, Breakpoint::new_standard()),
20734            (3, Breakpoint::new_standard()),
20735        ],
20736    );
20737
20738    editor.update_in(cx, |editor, window, cx| {
20739        editor.move_to_beginning(&MoveToBeginning, window, cx);
20740        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20741    });
20742
20743    let breakpoints = editor.update(cx, |editor, cx| {
20744        editor
20745            .breakpoint_store()
20746            .as_ref()
20747            .unwrap()
20748            .read(cx)
20749            .all_source_breakpoints(cx)
20750            .clone()
20751    });
20752
20753    assert_eq!(1, breakpoints.len());
20754    assert_breakpoint(
20755        &breakpoints,
20756        &abs_path,
20757        vec![(3, Breakpoint::new_standard())],
20758    );
20759
20760    editor.update_in(cx, |editor, window, cx| {
20761        editor.move_to_end(&MoveToEnd, window, cx);
20762        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20763    });
20764
20765    let breakpoints = editor.update(cx, |editor, cx| {
20766        editor
20767            .breakpoint_store()
20768            .as_ref()
20769            .unwrap()
20770            .read(cx)
20771            .all_source_breakpoints(cx)
20772            .clone()
20773    });
20774
20775    assert_eq!(0, breakpoints.len());
20776    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20777}
20778
20779#[gpui::test]
20780async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20781    init_test(cx, |_| {});
20782
20783    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20784
20785    let fs = FakeFs::new(cx.executor());
20786    fs.insert_tree(
20787        path!("/a"),
20788        json!({
20789            "main.rs": sample_text,
20790        }),
20791    )
20792    .await;
20793    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20794    let (workspace, cx) =
20795        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20796
20797    let worktree_id = workspace.update(cx, |workspace, cx| {
20798        workspace.project().update(cx, |project, cx| {
20799            project.worktrees(cx).next().unwrap().read(cx).id()
20800        })
20801    });
20802
20803    let buffer = project
20804        .update(cx, |project, cx| {
20805            project.open_buffer((worktree_id, "main.rs"), cx)
20806        })
20807        .await
20808        .unwrap();
20809
20810    let (editor, cx) = cx.add_window_view(|window, cx| {
20811        Editor::new(
20812            EditorMode::full(),
20813            MultiBuffer::build_from_buffer(buffer, cx),
20814            Some(project.clone()),
20815            window,
20816            cx,
20817        )
20818    });
20819
20820    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20821    let abs_path = project.read_with(cx, |project, cx| {
20822        project
20823            .absolute_path(&project_path, cx)
20824            .map(|path_buf| Arc::from(path_buf.to_owned()))
20825            .unwrap()
20826    });
20827
20828    editor.update_in(cx, |editor, window, cx| {
20829        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20830    });
20831
20832    let breakpoints = editor.update(cx, |editor, cx| {
20833        editor
20834            .breakpoint_store()
20835            .as_ref()
20836            .unwrap()
20837            .read(cx)
20838            .all_source_breakpoints(cx)
20839            .clone()
20840    });
20841
20842    assert_breakpoint(
20843        &breakpoints,
20844        &abs_path,
20845        vec![(0, Breakpoint::new_log("hello world"))],
20846    );
20847
20848    // Removing a log message from a log breakpoint should remove it
20849    editor.update_in(cx, |editor, window, cx| {
20850        add_log_breakpoint_at_cursor(editor, "", window, cx);
20851    });
20852
20853    let breakpoints = editor.update(cx, |editor, cx| {
20854        editor
20855            .breakpoint_store()
20856            .as_ref()
20857            .unwrap()
20858            .read(cx)
20859            .all_source_breakpoints(cx)
20860            .clone()
20861    });
20862
20863    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20864
20865    editor.update_in(cx, |editor, window, cx| {
20866        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20867        editor.move_to_end(&MoveToEnd, window, cx);
20868        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20869        // Not adding a log message to a standard breakpoint shouldn't remove it
20870        add_log_breakpoint_at_cursor(editor, "", window, cx);
20871    });
20872
20873    let breakpoints = editor.update(cx, |editor, cx| {
20874        editor
20875            .breakpoint_store()
20876            .as_ref()
20877            .unwrap()
20878            .read(cx)
20879            .all_source_breakpoints(cx)
20880            .clone()
20881    });
20882
20883    assert_breakpoint(
20884        &breakpoints,
20885        &abs_path,
20886        vec![
20887            (0, Breakpoint::new_standard()),
20888            (3, Breakpoint::new_standard()),
20889        ],
20890    );
20891
20892    editor.update_in(cx, |editor, window, cx| {
20893        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20894    });
20895
20896    let breakpoints = editor.update(cx, |editor, cx| {
20897        editor
20898            .breakpoint_store()
20899            .as_ref()
20900            .unwrap()
20901            .read(cx)
20902            .all_source_breakpoints(cx)
20903            .clone()
20904    });
20905
20906    assert_breakpoint(
20907        &breakpoints,
20908        &abs_path,
20909        vec![
20910            (0, Breakpoint::new_standard()),
20911            (3, Breakpoint::new_log("hello world")),
20912        ],
20913    );
20914
20915    editor.update_in(cx, |editor, window, cx| {
20916        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20917    });
20918
20919    let breakpoints = editor.update(cx, |editor, cx| {
20920        editor
20921            .breakpoint_store()
20922            .as_ref()
20923            .unwrap()
20924            .read(cx)
20925            .all_source_breakpoints(cx)
20926            .clone()
20927    });
20928
20929    assert_breakpoint(
20930        &breakpoints,
20931        &abs_path,
20932        vec![
20933            (0, Breakpoint::new_standard()),
20934            (3, Breakpoint::new_log("hello Earth!!")),
20935        ],
20936    );
20937}
20938
20939/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20940/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20941/// or when breakpoints were placed out of order. This tests for a regression too
20942#[gpui::test]
20943async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20944    init_test(cx, |_| {});
20945
20946    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20947    let fs = FakeFs::new(cx.executor());
20948    fs.insert_tree(
20949        path!("/a"),
20950        json!({
20951            "main.rs": sample_text,
20952        }),
20953    )
20954    .await;
20955    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20956    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20957    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20958
20959    let fs = FakeFs::new(cx.executor());
20960    fs.insert_tree(
20961        path!("/a"),
20962        json!({
20963            "main.rs": sample_text,
20964        }),
20965    )
20966    .await;
20967    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20968    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20969    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20970    let worktree_id = workspace
20971        .update(cx, |workspace, _window, cx| {
20972            workspace.project().update(cx, |project, cx| {
20973                project.worktrees(cx).next().unwrap().read(cx).id()
20974            })
20975        })
20976        .unwrap();
20977
20978    let buffer = project
20979        .update(cx, |project, cx| {
20980            project.open_buffer((worktree_id, "main.rs"), cx)
20981        })
20982        .await
20983        .unwrap();
20984
20985    let (editor, cx) = cx.add_window_view(|window, cx| {
20986        Editor::new(
20987            EditorMode::full(),
20988            MultiBuffer::build_from_buffer(buffer, cx),
20989            Some(project.clone()),
20990            window,
20991            cx,
20992        )
20993    });
20994
20995    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20996    let abs_path = project.read_with(cx, |project, cx| {
20997        project
20998            .absolute_path(&project_path, cx)
20999            .map(|path_buf| Arc::from(path_buf.to_owned()))
21000            .unwrap()
21001    });
21002
21003    // assert we can add breakpoint on the first line
21004    editor.update_in(cx, |editor, window, cx| {
21005        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21006        editor.move_to_end(&MoveToEnd, window, cx);
21007        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21008        editor.move_up(&MoveUp, window, cx);
21009        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21010    });
21011
21012    let breakpoints = editor.update(cx, |editor, cx| {
21013        editor
21014            .breakpoint_store()
21015            .as_ref()
21016            .unwrap()
21017            .read(cx)
21018            .all_source_breakpoints(cx)
21019            .clone()
21020    });
21021
21022    assert_eq!(1, breakpoints.len());
21023    assert_breakpoint(
21024        &breakpoints,
21025        &abs_path,
21026        vec![
21027            (0, Breakpoint::new_standard()),
21028            (2, Breakpoint::new_standard()),
21029            (3, Breakpoint::new_standard()),
21030        ],
21031    );
21032
21033    editor.update_in(cx, |editor, window, cx| {
21034        editor.move_to_beginning(&MoveToBeginning, window, cx);
21035        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21036        editor.move_to_end(&MoveToEnd, window, cx);
21037        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21038        // Disabling a breakpoint that doesn't exist should do nothing
21039        editor.move_up(&MoveUp, window, cx);
21040        editor.move_up(&MoveUp, window, cx);
21041        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21042    });
21043
21044    let breakpoints = editor.update(cx, |editor, cx| {
21045        editor
21046            .breakpoint_store()
21047            .as_ref()
21048            .unwrap()
21049            .read(cx)
21050            .all_source_breakpoints(cx)
21051            .clone()
21052    });
21053
21054    let disable_breakpoint = {
21055        let mut bp = Breakpoint::new_standard();
21056        bp.state = BreakpointState::Disabled;
21057        bp
21058    };
21059
21060    assert_eq!(1, breakpoints.len());
21061    assert_breakpoint(
21062        &breakpoints,
21063        &abs_path,
21064        vec![
21065            (0, disable_breakpoint.clone()),
21066            (2, Breakpoint::new_standard()),
21067            (3, disable_breakpoint.clone()),
21068        ],
21069    );
21070
21071    editor.update_in(cx, |editor, window, cx| {
21072        editor.move_to_beginning(&MoveToBeginning, window, cx);
21073        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21074        editor.move_to_end(&MoveToEnd, window, cx);
21075        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21076        editor.move_up(&MoveUp, window, cx);
21077        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21078    });
21079
21080    let breakpoints = editor.update(cx, |editor, cx| {
21081        editor
21082            .breakpoint_store()
21083            .as_ref()
21084            .unwrap()
21085            .read(cx)
21086            .all_source_breakpoints(cx)
21087            .clone()
21088    });
21089
21090    assert_eq!(1, breakpoints.len());
21091    assert_breakpoint(
21092        &breakpoints,
21093        &abs_path,
21094        vec![
21095            (0, Breakpoint::new_standard()),
21096            (2, disable_breakpoint),
21097            (3, Breakpoint::new_standard()),
21098        ],
21099    );
21100}
21101
21102#[gpui::test]
21103async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21104    init_test(cx, |_| {});
21105    let capabilities = lsp::ServerCapabilities {
21106        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21107            prepare_provider: Some(true),
21108            work_done_progress_options: Default::default(),
21109        })),
21110        ..Default::default()
21111    };
21112    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21113
21114    cx.set_state(indoc! {"
21115        struct Fˇoo {}
21116    "});
21117
21118    cx.update_editor(|editor, _, cx| {
21119        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21120        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21121        editor.highlight_background::<DocumentHighlightRead>(
21122            &[highlight_range],
21123            |theme| theme.colors().editor_document_highlight_read_background,
21124            cx,
21125        );
21126    });
21127
21128    let mut prepare_rename_handler = cx
21129        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21130            move |_, _, _| async move {
21131                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21132                    start: lsp::Position {
21133                        line: 0,
21134                        character: 7,
21135                    },
21136                    end: lsp::Position {
21137                        line: 0,
21138                        character: 10,
21139                    },
21140                })))
21141            },
21142        );
21143    let prepare_rename_task = cx
21144        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21145        .expect("Prepare rename was not started");
21146    prepare_rename_handler.next().await.unwrap();
21147    prepare_rename_task.await.expect("Prepare rename failed");
21148
21149    let mut rename_handler =
21150        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21151            let edit = lsp::TextEdit {
21152                range: lsp::Range {
21153                    start: lsp::Position {
21154                        line: 0,
21155                        character: 7,
21156                    },
21157                    end: lsp::Position {
21158                        line: 0,
21159                        character: 10,
21160                    },
21161                },
21162                new_text: "FooRenamed".to_string(),
21163            };
21164            Ok(Some(lsp::WorkspaceEdit::new(
21165                // Specify the same edit twice
21166                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21167            )))
21168        });
21169    let rename_task = cx
21170        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21171        .expect("Confirm rename was not started");
21172    rename_handler.next().await.unwrap();
21173    rename_task.await.expect("Confirm rename failed");
21174    cx.run_until_parked();
21175
21176    // Despite two edits, only one is actually applied as those are identical
21177    cx.assert_editor_state(indoc! {"
21178        struct FooRenamedˇ {}
21179    "});
21180}
21181
21182#[gpui::test]
21183async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21184    init_test(cx, |_| {});
21185    // These capabilities indicate that the server does not support prepare rename.
21186    let capabilities = lsp::ServerCapabilities {
21187        rename_provider: Some(lsp::OneOf::Left(true)),
21188        ..Default::default()
21189    };
21190    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21191
21192    cx.set_state(indoc! {"
21193        struct Fˇoo {}
21194    "});
21195
21196    cx.update_editor(|editor, _window, cx| {
21197        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21198        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21199        editor.highlight_background::<DocumentHighlightRead>(
21200            &[highlight_range],
21201            |theme| theme.colors().editor_document_highlight_read_background,
21202            cx,
21203        );
21204    });
21205
21206    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21207        .expect("Prepare rename was not started")
21208        .await
21209        .expect("Prepare rename failed");
21210
21211    let mut rename_handler =
21212        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21213            let edit = lsp::TextEdit {
21214                range: lsp::Range {
21215                    start: lsp::Position {
21216                        line: 0,
21217                        character: 7,
21218                    },
21219                    end: lsp::Position {
21220                        line: 0,
21221                        character: 10,
21222                    },
21223                },
21224                new_text: "FooRenamed".to_string(),
21225            };
21226            Ok(Some(lsp::WorkspaceEdit::new(
21227                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21228            )))
21229        });
21230    let rename_task = cx
21231        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21232        .expect("Confirm rename was not started");
21233    rename_handler.next().await.unwrap();
21234    rename_task.await.expect("Confirm rename failed");
21235    cx.run_until_parked();
21236
21237    // Correct range is renamed, as `surrounding_word` is used to find it.
21238    cx.assert_editor_state(indoc! {"
21239        struct FooRenamedˇ {}
21240    "});
21241}
21242
21243#[gpui::test]
21244async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21245    init_test(cx, |_| {});
21246    let mut cx = EditorTestContext::new(cx).await;
21247
21248    let language = Arc::new(
21249        Language::new(
21250            LanguageConfig::default(),
21251            Some(tree_sitter_html::LANGUAGE.into()),
21252        )
21253        .with_brackets_query(
21254            r#"
21255            ("<" @open "/>" @close)
21256            ("</" @open ">" @close)
21257            ("<" @open ">" @close)
21258            ("\"" @open "\"" @close)
21259            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21260        "#,
21261        )
21262        .unwrap(),
21263    );
21264    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21265
21266    cx.set_state(indoc! {"
21267        <span>ˇ</span>
21268    "});
21269    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21270    cx.assert_editor_state(indoc! {"
21271        <span>
21272        ˇ
21273        </span>
21274    "});
21275
21276    cx.set_state(indoc! {"
21277        <span><span></span>ˇ</span>
21278    "});
21279    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21280    cx.assert_editor_state(indoc! {"
21281        <span><span></span>
21282        ˇ</span>
21283    "});
21284
21285    cx.set_state(indoc! {"
21286        <span>ˇ
21287        </span>
21288    "});
21289    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21290    cx.assert_editor_state(indoc! {"
21291        <span>
21292        ˇ
21293        </span>
21294    "});
21295}
21296
21297#[gpui::test(iterations = 10)]
21298async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21299    init_test(cx, |_| {});
21300
21301    let fs = FakeFs::new(cx.executor());
21302    fs.insert_tree(
21303        path!("/dir"),
21304        json!({
21305            "a.ts": "a",
21306        }),
21307    )
21308    .await;
21309
21310    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21311    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21312    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21313
21314    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21315    language_registry.add(Arc::new(Language::new(
21316        LanguageConfig {
21317            name: "TypeScript".into(),
21318            matcher: LanguageMatcher {
21319                path_suffixes: vec!["ts".to_string()],
21320                ..Default::default()
21321            },
21322            ..Default::default()
21323        },
21324        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21325    )));
21326    let mut fake_language_servers = language_registry.register_fake_lsp(
21327        "TypeScript",
21328        FakeLspAdapter {
21329            capabilities: lsp::ServerCapabilities {
21330                code_lens_provider: Some(lsp::CodeLensOptions {
21331                    resolve_provider: Some(true),
21332                }),
21333                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21334                    commands: vec!["_the/command".to_string()],
21335                    ..lsp::ExecuteCommandOptions::default()
21336                }),
21337                ..lsp::ServerCapabilities::default()
21338            },
21339            ..FakeLspAdapter::default()
21340        },
21341    );
21342
21343    let editor = workspace
21344        .update(cx, |workspace, window, cx| {
21345            workspace.open_abs_path(
21346                PathBuf::from(path!("/dir/a.ts")),
21347                OpenOptions::default(),
21348                window,
21349                cx,
21350            )
21351        })
21352        .unwrap()
21353        .await
21354        .unwrap()
21355        .downcast::<Editor>()
21356        .unwrap();
21357    cx.executor().run_until_parked();
21358
21359    let fake_server = fake_language_servers.next().await.unwrap();
21360
21361    let buffer = editor.update(cx, |editor, cx| {
21362        editor
21363            .buffer()
21364            .read(cx)
21365            .as_singleton()
21366            .expect("have opened a single file by path")
21367    });
21368
21369    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21370    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21371    drop(buffer_snapshot);
21372    let actions = cx
21373        .update_window(*workspace, |_, window, cx| {
21374            project.code_actions(&buffer, anchor..anchor, window, cx)
21375        })
21376        .unwrap();
21377
21378    fake_server
21379        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21380            Ok(Some(vec![
21381                lsp::CodeLens {
21382                    range: lsp::Range::default(),
21383                    command: Some(lsp::Command {
21384                        title: "Code lens command".to_owned(),
21385                        command: "_the/command".to_owned(),
21386                        arguments: None,
21387                    }),
21388                    data: None,
21389                },
21390                lsp::CodeLens {
21391                    range: lsp::Range::default(),
21392                    command: Some(lsp::Command {
21393                        title: "Command not in capabilities".to_owned(),
21394                        command: "not in capabilities".to_owned(),
21395                        arguments: None,
21396                    }),
21397                    data: None,
21398                },
21399                lsp::CodeLens {
21400                    range: lsp::Range {
21401                        start: lsp::Position {
21402                            line: 1,
21403                            character: 1,
21404                        },
21405                        end: lsp::Position {
21406                            line: 1,
21407                            character: 1,
21408                        },
21409                    },
21410                    command: Some(lsp::Command {
21411                        title: "Command not in range".to_owned(),
21412                        command: "_the/command".to_owned(),
21413                        arguments: None,
21414                    }),
21415                    data: None,
21416                },
21417            ]))
21418        })
21419        .next()
21420        .await;
21421
21422    let actions = actions.await.unwrap();
21423    assert_eq!(
21424        actions.len(),
21425        1,
21426        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21427    );
21428    let action = actions[0].clone();
21429    let apply = project.update(cx, |project, cx| {
21430        project.apply_code_action(buffer.clone(), action, true, cx)
21431    });
21432
21433    // Resolving the code action does not populate its edits. In absence of
21434    // edits, we must execute the given command.
21435    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21436        |mut lens, _| async move {
21437            let lens_command = lens.command.as_mut().expect("should have a command");
21438            assert_eq!(lens_command.title, "Code lens command");
21439            lens_command.arguments = Some(vec![json!("the-argument")]);
21440            Ok(lens)
21441        },
21442    );
21443
21444    // While executing the command, the language server sends the editor
21445    // a `workspaceEdit` request.
21446    fake_server
21447        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21448            let fake = fake_server.clone();
21449            move |params, _| {
21450                assert_eq!(params.command, "_the/command");
21451                let fake = fake.clone();
21452                async move {
21453                    fake.server
21454                        .request::<lsp::request::ApplyWorkspaceEdit>(
21455                            lsp::ApplyWorkspaceEditParams {
21456                                label: None,
21457                                edit: lsp::WorkspaceEdit {
21458                                    changes: Some(
21459                                        [(
21460                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21461                                            vec![lsp::TextEdit {
21462                                                range: lsp::Range::new(
21463                                                    lsp::Position::new(0, 0),
21464                                                    lsp::Position::new(0, 0),
21465                                                ),
21466                                                new_text: "X".into(),
21467                                            }],
21468                                        )]
21469                                        .into_iter()
21470                                        .collect(),
21471                                    ),
21472                                    ..lsp::WorkspaceEdit::default()
21473                                },
21474                            },
21475                        )
21476                        .await
21477                        .into_response()
21478                        .unwrap();
21479                    Ok(Some(json!(null)))
21480                }
21481            }
21482        })
21483        .next()
21484        .await;
21485
21486    // Applying the code lens command returns a project transaction containing the edits
21487    // sent by the language server in its `workspaceEdit` request.
21488    let transaction = apply.await.unwrap();
21489    assert!(transaction.0.contains_key(&buffer));
21490    buffer.update(cx, |buffer, cx| {
21491        assert_eq!(buffer.text(), "Xa");
21492        buffer.undo(cx);
21493        assert_eq!(buffer.text(), "a");
21494    });
21495
21496    let actions_after_edits = cx
21497        .update_window(*workspace, |_, window, cx| {
21498            project.code_actions(&buffer, anchor..anchor, window, cx)
21499        })
21500        .unwrap()
21501        .await
21502        .unwrap();
21503    assert_eq!(
21504        actions, actions_after_edits,
21505        "For the same selection, same code lens actions should be returned"
21506    );
21507
21508    let _responses =
21509        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21510            panic!("No more code lens requests are expected");
21511        });
21512    editor.update_in(cx, |editor, window, cx| {
21513        editor.select_all(&SelectAll, window, cx);
21514    });
21515    cx.executor().run_until_parked();
21516    let new_actions = cx
21517        .update_window(*workspace, |_, window, cx| {
21518            project.code_actions(&buffer, anchor..anchor, window, cx)
21519        })
21520        .unwrap()
21521        .await
21522        .unwrap();
21523    assert_eq!(
21524        actions, new_actions,
21525        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21526    );
21527}
21528
21529#[gpui::test]
21530async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21531    init_test(cx, |_| {});
21532
21533    let fs = FakeFs::new(cx.executor());
21534    let main_text = r#"fn main() {
21535println!("1");
21536println!("2");
21537println!("3");
21538println!("4");
21539println!("5");
21540}"#;
21541    let lib_text = "mod foo {}";
21542    fs.insert_tree(
21543        path!("/a"),
21544        json!({
21545            "lib.rs": lib_text,
21546            "main.rs": main_text,
21547        }),
21548    )
21549    .await;
21550
21551    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21552    let (workspace, cx) =
21553        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21554    let worktree_id = workspace.update(cx, |workspace, cx| {
21555        workspace.project().update(cx, |project, cx| {
21556            project.worktrees(cx).next().unwrap().read(cx).id()
21557        })
21558    });
21559
21560    let expected_ranges = vec![
21561        Point::new(0, 0)..Point::new(0, 0),
21562        Point::new(1, 0)..Point::new(1, 1),
21563        Point::new(2, 0)..Point::new(2, 2),
21564        Point::new(3, 0)..Point::new(3, 3),
21565    ];
21566
21567    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21568    let editor_1 = workspace
21569        .update_in(cx, |workspace, window, cx| {
21570            workspace.open_path(
21571                (worktree_id, "main.rs"),
21572                Some(pane_1.downgrade()),
21573                true,
21574                window,
21575                cx,
21576            )
21577        })
21578        .unwrap()
21579        .await
21580        .downcast::<Editor>()
21581        .unwrap();
21582    pane_1.update(cx, |pane, cx| {
21583        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21584        open_editor.update(cx, |editor, cx| {
21585            assert_eq!(
21586                editor.display_text(cx),
21587                main_text,
21588                "Original main.rs text on initial open",
21589            );
21590            assert_eq!(
21591                editor
21592                    .selections
21593                    .all::<Point>(cx)
21594                    .into_iter()
21595                    .map(|s| s.range())
21596                    .collect::<Vec<_>>(),
21597                vec![Point::zero()..Point::zero()],
21598                "Default selections on initial open",
21599            );
21600        })
21601    });
21602    editor_1.update_in(cx, |editor, window, cx| {
21603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21604            s.select_ranges(expected_ranges.clone());
21605        });
21606    });
21607
21608    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21609        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21610    });
21611    let editor_2 = workspace
21612        .update_in(cx, |workspace, window, cx| {
21613            workspace.open_path(
21614                (worktree_id, "main.rs"),
21615                Some(pane_2.downgrade()),
21616                true,
21617                window,
21618                cx,
21619            )
21620        })
21621        .unwrap()
21622        .await
21623        .downcast::<Editor>()
21624        .unwrap();
21625    pane_2.update(cx, |pane, cx| {
21626        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21627        open_editor.update(cx, |editor, cx| {
21628            assert_eq!(
21629                editor.display_text(cx),
21630                main_text,
21631                "Original main.rs text on initial open in another panel",
21632            );
21633            assert_eq!(
21634                editor
21635                    .selections
21636                    .all::<Point>(cx)
21637                    .into_iter()
21638                    .map(|s| s.range())
21639                    .collect::<Vec<_>>(),
21640                vec![Point::zero()..Point::zero()],
21641                "Default selections on initial open in another panel",
21642            );
21643        })
21644    });
21645
21646    editor_2.update_in(cx, |editor, window, cx| {
21647        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21648    });
21649
21650    let _other_editor_1 = workspace
21651        .update_in(cx, |workspace, window, cx| {
21652            workspace.open_path(
21653                (worktree_id, "lib.rs"),
21654                Some(pane_1.downgrade()),
21655                true,
21656                window,
21657                cx,
21658            )
21659        })
21660        .unwrap()
21661        .await
21662        .downcast::<Editor>()
21663        .unwrap();
21664    pane_1
21665        .update_in(cx, |pane, window, cx| {
21666            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21667        })
21668        .await
21669        .unwrap();
21670    drop(editor_1);
21671    pane_1.update(cx, |pane, cx| {
21672        pane.active_item()
21673            .unwrap()
21674            .downcast::<Editor>()
21675            .unwrap()
21676            .update(cx, |editor, cx| {
21677                assert_eq!(
21678                    editor.display_text(cx),
21679                    lib_text,
21680                    "Other file should be open and active",
21681                );
21682            });
21683        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21684    });
21685
21686    let _other_editor_2 = workspace
21687        .update_in(cx, |workspace, window, cx| {
21688            workspace.open_path(
21689                (worktree_id, "lib.rs"),
21690                Some(pane_2.downgrade()),
21691                true,
21692                window,
21693                cx,
21694            )
21695        })
21696        .unwrap()
21697        .await
21698        .downcast::<Editor>()
21699        .unwrap();
21700    pane_2
21701        .update_in(cx, |pane, window, cx| {
21702            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21703        })
21704        .await
21705        .unwrap();
21706    drop(editor_2);
21707    pane_2.update(cx, |pane, cx| {
21708        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21709        open_editor.update(cx, |editor, cx| {
21710            assert_eq!(
21711                editor.display_text(cx),
21712                lib_text,
21713                "Other file should be open and active in another panel too",
21714            );
21715        });
21716        assert_eq!(
21717            pane.items().count(),
21718            1,
21719            "No other editors should be open in another pane",
21720        );
21721    });
21722
21723    let _editor_1_reopened = workspace
21724        .update_in(cx, |workspace, window, cx| {
21725            workspace.open_path(
21726                (worktree_id, "main.rs"),
21727                Some(pane_1.downgrade()),
21728                true,
21729                window,
21730                cx,
21731            )
21732        })
21733        .unwrap()
21734        .await
21735        .downcast::<Editor>()
21736        .unwrap();
21737    let _editor_2_reopened = workspace
21738        .update_in(cx, |workspace, window, cx| {
21739            workspace.open_path(
21740                (worktree_id, "main.rs"),
21741                Some(pane_2.downgrade()),
21742                true,
21743                window,
21744                cx,
21745            )
21746        })
21747        .unwrap()
21748        .await
21749        .downcast::<Editor>()
21750        .unwrap();
21751    pane_1.update(cx, |pane, cx| {
21752        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21753        open_editor.update(cx, |editor, cx| {
21754            assert_eq!(
21755                editor.display_text(cx),
21756                main_text,
21757                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21758            );
21759            assert_eq!(
21760                editor
21761                    .selections
21762                    .all::<Point>(cx)
21763                    .into_iter()
21764                    .map(|s| s.range())
21765                    .collect::<Vec<_>>(),
21766                expected_ranges,
21767                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21768            );
21769        })
21770    });
21771    pane_2.update(cx, |pane, cx| {
21772        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21773        open_editor.update(cx, |editor, cx| {
21774            assert_eq!(
21775                editor.display_text(cx),
21776                r#"fn main() {
21777⋯rintln!("1");
21778⋯intln!("2");
21779⋯ntln!("3");
21780println!("4");
21781println!("5");
21782}"#,
21783                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21784            );
21785            assert_eq!(
21786                editor
21787                    .selections
21788                    .all::<Point>(cx)
21789                    .into_iter()
21790                    .map(|s| s.range())
21791                    .collect::<Vec<_>>(),
21792                vec![Point::zero()..Point::zero()],
21793                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21794            );
21795        })
21796    });
21797}
21798
21799#[gpui::test]
21800async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21801    init_test(cx, |_| {});
21802
21803    let fs = FakeFs::new(cx.executor());
21804    let main_text = r#"fn main() {
21805println!("1");
21806println!("2");
21807println!("3");
21808println!("4");
21809println!("5");
21810}"#;
21811    let lib_text = "mod foo {}";
21812    fs.insert_tree(
21813        path!("/a"),
21814        json!({
21815            "lib.rs": lib_text,
21816            "main.rs": main_text,
21817        }),
21818    )
21819    .await;
21820
21821    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21822    let (workspace, cx) =
21823        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21824    let worktree_id = workspace.update(cx, |workspace, cx| {
21825        workspace.project().update(cx, |project, cx| {
21826            project.worktrees(cx).next().unwrap().read(cx).id()
21827        })
21828    });
21829
21830    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21831    let editor = workspace
21832        .update_in(cx, |workspace, window, cx| {
21833            workspace.open_path(
21834                (worktree_id, "main.rs"),
21835                Some(pane.downgrade()),
21836                true,
21837                window,
21838                cx,
21839            )
21840        })
21841        .unwrap()
21842        .await
21843        .downcast::<Editor>()
21844        .unwrap();
21845    pane.update(cx, |pane, cx| {
21846        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21847        open_editor.update(cx, |editor, cx| {
21848            assert_eq!(
21849                editor.display_text(cx),
21850                main_text,
21851                "Original main.rs text on initial open",
21852            );
21853        })
21854    });
21855    editor.update_in(cx, |editor, window, cx| {
21856        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21857    });
21858
21859    cx.update_global(|store: &mut SettingsStore, cx| {
21860        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21861            s.restore_on_file_reopen = Some(false);
21862        });
21863    });
21864    editor.update_in(cx, |editor, window, cx| {
21865        editor.fold_ranges(
21866            vec![
21867                Point::new(1, 0)..Point::new(1, 1),
21868                Point::new(2, 0)..Point::new(2, 2),
21869                Point::new(3, 0)..Point::new(3, 3),
21870            ],
21871            false,
21872            window,
21873            cx,
21874        );
21875    });
21876    pane.update_in(cx, |pane, window, cx| {
21877        pane.close_all_items(&CloseAllItems::default(), window, cx)
21878    })
21879    .await
21880    .unwrap();
21881    pane.update(cx, |pane, _| {
21882        assert!(pane.active_item().is_none());
21883    });
21884    cx.update_global(|store: &mut SettingsStore, cx| {
21885        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21886            s.restore_on_file_reopen = Some(true);
21887        });
21888    });
21889
21890    let _editor_reopened = workspace
21891        .update_in(cx, |workspace, window, cx| {
21892            workspace.open_path(
21893                (worktree_id, "main.rs"),
21894                Some(pane.downgrade()),
21895                true,
21896                window,
21897                cx,
21898            )
21899        })
21900        .unwrap()
21901        .await
21902        .downcast::<Editor>()
21903        .unwrap();
21904    pane.update(cx, |pane, cx| {
21905        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21906        open_editor.update(cx, |editor, cx| {
21907            assert_eq!(
21908                editor.display_text(cx),
21909                main_text,
21910                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21911            );
21912        })
21913    });
21914}
21915
21916#[gpui::test]
21917async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21918    struct EmptyModalView {
21919        focus_handle: gpui::FocusHandle,
21920    }
21921    impl EventEmitter<DismissEvent> for EmptyModalView {}
21922    impl Render for EmptyModalView {
21923        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21924            div()
21925        }
21926    }
21927    impl Focusable for EmptyModalView {
21928        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21929            self.focus_handle.clone()
21930        }
21931    }
21932    impl workspace::ModalView for EmptyModalView {}
21933    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21934        EmptyModalView {
21935            focus_handle: cx.focus_handle(),
21936        }
21937    }
21938
21939    init_test(cx, |_| {});
21940
21941    let fs = FakeFs::new(cx.executor());
21942    let project = Project::test(fs, [], cx).await;
21943    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21944    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21945    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21946    let editor = cx.new_window_entity(|window, cx| {
21947        Editor::new(
21948            EditorMode::full(),
21949            buffer,
21950            Some(project.clone()),
21951            window,
21952            cx,
21953        )
21954    });
21955    workspace
21956        .update(cx, |workspace, window, cx| {
21957            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21958        })
21959        .unwrap();
21960    editor.update_in(cx, |editor, window, cx| {
21961        editor.open_context_menu(&OpenContextMenu, window, cx);
21962        assert!(editor.mouse_context_menu.is_some());
21963    });
21964    workspace
21965        .update(cx, |workspace, window, cx| {
21966            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21967        })
21968        .unwrap();
21969    cx.read(|cx| {
21970        assert!(editor.read(cx).mouse_context_menu.is_none());
21971    });
21972}
21973
21974#[gpui::test]
21975async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21976    init_test(cx, |_| {});
21977
21978    let fs = FakeFs::new(cx.executor());
21979    fs.insert_file(path!("/file.html"), Default::default())
21980        .await;
21981
21982    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21983
21984    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21985    let html_language = Arc::new(Language::new(
21986        LanguageConfig {
21987            name: "HTML".into(),
21988            matcher: LanguageMatcher {
21989                path_suffixes: vec!["html".to_string()],
21990                ..LanguageMatcher::default()
21991            },
21992            brackets: BracketPairConfig {
21993                pairs: vec![BracketPair {
21994                    start: "<".into(),
21995                    end: ">".into(),
21996                    close: true,
21997                    ..Default::default()
21998                }],
21999                ..Default::default()
22000            },
22001            ..Default::default()
22002        },
22003        Some(tree_sitter_html::LANGUAGE.into()),
22004    ));
22005    language_registry.add(html_language);
22006    let mut fake_servers = language_registry.register_fake_lsp(
22007        "HTML",
22008        FakeLspAdapter {
22009            capabilities: lsp::ServerCapabilities {
22010                completion_provider: Some(lsp::CompletionOptions {
22011                    resolve_provider: Some(true),
22012                    ..Default::default()
22013                }),
22014                ..Default::default()
22015            },
22016            ..Default::default()
22017        },
22018    );
22019
22020    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22021    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22022
22023    let worktree_id = workspace
22024        .update(cx, |workspace, _window, cx| {
22025            workspace.project().update(cx, |project, cx| {
22026                project.worktrees(cx).next().unwrap().read(cx).id()
22027            })
22028        })
22029        .unwrap();
22030    project
22031        .update(cx, |project, cx| {
22032            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22033        })
22034        .await
22035        .unwrap();
22036    let editor = workspace
22037        .update(cx, |workspace, window, cx| {
22038            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22039        })
22040        .unwrap()
22041        .await
22042        .unwrap()
22043        .downcast::<Editor>()
22044        .unwrap();
22045
22046    let fake_server = fake_servers.next().await.unwrap();
22047    editor.update_in(cx, |editor, window, cx| {
22048        editor.set_text("<ad></ad>", window, cx);
22049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22050            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22051        });
22052        let Some((buffer, _)) = editor
22053            .buffer
22054            .read(cx)
22055            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22056        else {
22057            panic!("Failed to get buffer for selection position");
22058        };
22059        let buffer = buffer.read(cx);
22060        let buffer_id = buffer.remote_id();
22061        let opening_range =
22062            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22063        let closing_range =
22064            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22065        let mut linked_ranges = HashMap::default();
22066        linked_ranges.insert(
22067            buffer_id,
22068            vec![(opening_range.clone(), vec![closing_range.clone()])],
22069        );
22070        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22071    });
22072    let mut completion_handle =
22073        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22074            Ok(Some(lsp::CompletionResponse::Array(vec![
22075                lsp::CompletionItem {
22076                    label: "head".to_string(),
22077                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22078                        lsp::InsertReplaceEdit {
22079                            new_text: "head".to_string(),
22080                            insert: lsp::Range::new(
22081                                lsp::Position::new(0, 1),
22082                                lsp::Position::new(0, 3),
22083                            ),
22084                            replace: lsp::Range::new(
22085                                lsp::Position::new(0, 1),
22086                                lsp::Position::new(0, 3),
22087                            ),
22088                        },
22089                    )),
22090                    ..Default::default()
22091                },
22092            ])))
22093        });
22094    editor.update_in(cx, |editor, window, cx| {
22095        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22096    });
22097    cx.run_until_parked();
22098    completion_handle.next().await.unwrap();
22099    editor.update(cx, |editor, _| {
22100        assert!(
22101            editor.context_menu_visible(),
22102            "Completion menu should be visible"
22103        );
22104    });
22105    editor.update_in(cx, |editor, window, cx| {
22106        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22107    });
22108    cx.executor().run_until_parked();
22109    editor.update(cx, |editor, cx| {
22110        assert_eq!(editor.text(cx), "<head></head>");
22111    });
22112}
22113
22114#[gpui::test]
22115async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22116    init_test(cx, |_| {});
22117
22118    let fs = FakeFs::new(cx.executor());
22119    fs.insert_tree(
22120        path!("/root"),
22121        json!({
22122            "a": {
22123                "main.rs": "fn main() {}",
22124            },
22125            "foo": {
22126                "bar": {
22127                    "external_file.rs": "pub mod external {}",
22128                }
22129            }
22130        }),
22131    )
22132    .await;
22133
22134    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22135    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22136    language_registry.add(rust_lang());
22137    let _fake_servers = language_registry.register_fake_lsp(
22138        "Rust",
22139        FakeLspAdapter {
22140            ..FakeLspAdapter::default()
22141        },
22142    );
22143    let (workspace, cx) =
22144        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22145    let worktree_id = workspace.update(cx, |workspace, cx| {
22146        workspace.project().update(cx, |project, cx| {
22147            project.worktrees(cx).next().unwrap().read(cx).id()
22148        })
22149    });
22150
22151    let assert_language_servers_count =
22152        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22153            project.update(cx, |project, cx| {
22154                let current = project
22155                    .lsp_store()
22156                    .read(cx)
22157                    .as_local()
22158                    .unwrap()
22159                    .language_servers
22160                    .len();
22161                assert_eq!(expected, current, "{context}");
22162            });
22163        };
22164
22165    assert_language_servers_count(
22166        0,
22167        "No servers should be running before any file is open",
22168        cx,
22169    );
22170    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22171    let main_editor = workspace
22172        .update_in(cx, |workspace, window, cx| {
22173            workspace.open_path(
22174                (worktree_id, "main.rs"),
22175                Some(pane.downgrade()),
22176                true,
22177                window,
22178                cx,
22179            )
22180        })
22181        .unwrap()
22182        .await
22183        .downcast::<Editor>()
22184        .unwrap();
22185    pane.update(cx, |pane, cx| {
22186        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22187        open_editor.update(cx, |editor, cx| {
22188            assert_eq!(
22189                editor.display_text(cx),
22190                "fn main() {}",
22191                "Original main.rs text on initial open",
22192            );
22193        });
22194        assert_eq!(open_editor, main_editor);
22195    });
22196    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22197
22198    let external_editor = workspace
22199        .update_in(cx, |workspace, window, cx| {
22200            workspace.open_abs_path(
22201                PathBuf::from("/root/foo/bar/external_file.rs"),
22202                OpenOptions::default(),
22203                window,
22204                cx,
22205            )
22206        })
22207        .await
22208        .expect("opening external file")
22209        .downcast::<Editor>()
22210        .expect("downcasted external file's open element to editor");
22211    pane.update(cx, |pane, cx| {
22212        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22213        open_editor.update(cx, |editor, cx| {
22214            assert_eq!(
22215                editor.display_text(cx),
22216                "pub mod external {}",
22217                "External file is open now",
22218            );
22219        });
22220        assert_eq!(open_editor, external_editor);
22221    });
22222    assert_language_servers_count(
22223        1,
22224        "Second, external, *.rs file should join the existing server",
22225        cx,
22226    );
22227
22228    pane.update_in(cx, |pane, window, cx| {
22229        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22230    })
22231    .await
22232    .unwrap();
22233    pane.update_in(cx, |pane, window, cx| {
22234        pane.navigate_backward(window, cx);
22235    });
22236    cx.run_until_parked();
22237    pane.update(cx, |pane, cx| {
22238        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22239        open_editor.update(cx, |editor, cx| {
22240            assert_eq!(
22241                editor.display_text(cx),
22242                "pub mod external {}",
22243                "External file is open now",
22244            );
22245        });
22246    });
22247    assert_language_servers_count(
22248        1,
22249        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22250        cx,
22251    );
22252
22253    cx.update(|_, cx| {
22254        workspace::reload(&workspace::Reload::default(), cx);
22255    });
22256    assert_language_servers_count(
22257        1,
22258        "After reloading the worktree with local and external files opened, only one project should be started",
22259        cx,
22260    );
22261}
22262
22263#[gpui::test]
22264async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22265    init_test(cx, |_| {});
22266
22267    let mut cx = EditorTestContext::new(cx).await;
22268    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22269    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22270
22271    // test cursor move to start of each line on tab
22272    // for `if`, `elif`, `else`, `while`, `with` and `for`
22273    cx.set_state(indoc! {"
22274        def main():
22275        ˇ    for item in items:
22276        ˇ        while item.active:
22277        ˇ            if item.value > 10:
22278        ˇ                continue
22279        ˇ            elif item.value < 0:
22280        ˇ                break
22281        ˇ            else:
22282        ˇ                with item.context() as ctx:
22283        ˇ                    yield count
22284        ˇ        else:
22285        ˇ            log('while else')
22286        ˇ    else:
22287        ˇ        log('for else')
22288    "});
22289    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22290    cx.assert_editor_state(indoc! {"
22291        def main():
22292            ˇfor item in items:
22293                ˇwhile item.active:
22294                    ˇif item.value > 10:
22295                        ˇcontinue
22296                    ˇelif item.value < 0:
22297                        ˇbreak
22298                    ˇelse:
22299                        ˇwith item.context() as ctx:
22300                            ˇyield count
22301                ˇelse:
22302                    ˇlog('while else')
22303            ˇelse:
22304                ˇlog('for else')
22305    "});
22306    // test relative indent is preserved when tab
22307    // for `if`, `elif`, `else`, `while`, `with` and `for`
22308    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22309    cx.assert_editor_state(indoc! {"
22310        def main():
22311                ˇfor item in items:
22312                    ˇwhile item.active:
22313                        ˇif item.value > 10:
22314                            ˇcontinue
22315                        ˇelif item.value < 0:
22316                            ˇbreak
22317                        ˇelse:
22318                            ˇwith item.context() as ctx:
22319                                ˇyield count
22320                    ˇelse:
22321                        ˇlog('while else')
22322                ˇelse:
22323                    ˇlog('for else')
22324    "});
22325
22326    // test cursor move to start of each line on tab
22327    // for `try`, `except`, `else`, `finally`, `match` and `def`
22328    cx.set_state(indoc! {"
22329        def main():
22330        ˇ    try:
22331        ˇ        fetch()
22332        ˇ    except ValueError:
22333        ˇ        handle_error()
22334        ˇ    else:
22335        ˇ        match value:
22336        ˇ            case _:
22337        ˇ    finally:
22338        ˇ        def status():
22339        ˇ            return 0
22340    "});
22341    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22342    cx.assert_editor_state(indoc! {"
22343        def main():
22344            ˇtry:
22345                ˇfetch()
22346            ˇexcept ValueError:
22347                ˇhandle_error()
22348            ˇelse:
22349                ˇmatch value:
22350                    ˇcase _:
22351            ˇfinally:
22352                ˇdef status():
22353                    ˇreturn 0
22354    "});
22355    // test relative indent is preserved when tab
22356    // for `try`, `except`, `else`, `finally`, `match` and `def`
22357    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22358    cx.assert_editor_state(indoc! {"
22359        def main():
22360                ˇtry:
22361                    ˇfetch()
22362                ˇexcept ValueError:
22363                    ˇhandle_error()
22364                ˇelse:
22365                    ˇmatch value:
22366                        ˇcase _:
22367                ˇfinally:
22368                    ˇdef status():
22369                        ˇreturn 0
22370    "});
22371}
22372
22373#[gpui::test]
22374async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22375    init_test(cx, |_| {});
22376
22377    let mut cx = EditorTestContext::new(cx).await;
22378    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22380
22381    // test `else` auto outdents when typed inside `if` block
22382    cx.set_state(indoc! {"
22383        def main():
22384            if i == 2:
22385                return
22386                ˇ
22387    "});
22388    cx.update_editor(|editor, window, cx| {
22389        editor.handle_input("else:", window, cx);
22390    });
22391    cx.assert_editor_state(indoc! {"
22392        def main():
22393            if i == 2:
22394                return
22395            else:ˇ
22396    "});
22397
22398    // test `except` auto outdents when typed inside `try` block
22399    cx.set_state(indoc! {"
22400        def main():
22401            try:
22402                i = 2
22403                ˇ
22404    "});
22405    cx.update_editor(|editor, window, cx| {
22406        editor.handle_input("except:", window, cx);
22407    });
22408    cx.assert_editor_state(indoc! {"
22409        def main():
22410            try:
22411                i = 2
22412            except:ˇ
22413    "});
22414
22415    // test `else` auto outdents when typed inside `except` block
22416    cx.set_state(indoc! {"
22417        def main():
22418            try:
22419                i = 2
22420            except:
22421                j = 2
22422                ˇ
22423    "});
22424    cx.update_editor(|editor, window, cx| {
22425        editor.handle_input("else:", window, cx);
22426    });
22427    cx.assert_editor_state(indoc! {"
22428        def main():
22429            try:
22430                i = 2
22431            except:
22432                j = 2
22433            else:ˇ
22434    "});
22435
22436    // test `finally` auto outdents when typed inside `else` block
22437    cx.set_state(indoc! {"
22438        def main():
22439            try:
22440                i = 2
22441            except:
22442                j = 2
22443            else:
22444                k = 2
22445                ˇ
22446    "});
22447    cx.update_editor(|editor, window, cx| {
22448        editor.handle_input("finally:", window, cx);
22449    });
22450    cx.assert_editor_state(indoc! {"
22451        def main():
22452            try:
22453                i = 2
22454            except:
22455                j = 2
22456            else:
22457                k = 2
22458            finally:ˇ
22459    "});
22460
22461    // test `else` does not outdents when typed inside `except` block right after for block
22462    cx.set_state(indoc! {"
22463        def main():
22464            try:
22465                i = 2
22466            except:
22467                for i in range(n):
22468                    pass
22469                ˇ
22470    "});
22471    cx.update_editor(|editor, window, cx| {
22472        editor.handle_input("else:", window, cx);
22473    });
22474    cx.assert_editor_state(indoc! {"
22475        def main():
22476            try:
22477                i = 2
22478            except:
22479                for i in range(n):
22480                    pass
22481                else:ˇ
22482    "});
22483
22484    // test `finally` auto outdents when typed inside `else` block right after for block
22485    cx.set_state(indoc! {"
22486        def main():
22487            try:
22488                i = 2
22489            except:
22490                j = 2
22491            else:
22492                for i in range(n):
22493                    pass
22494                ˇ
22495    "});
22496    cx.update_editor(|editor, window, cx| {
22497        editor.handle_input("finally:", window, cx);
22498    });
22499    cx.assert_editor_state(indoc! {"
22500        def main():
22501            try:
22502                i = 2
22503            except:
22504                j = 2
22505            else:
22506                for i in range(n):
22507                    pass
22508            finally:ˇ
22509    "});
22510
22511    // test `except` outdents to inner "try" block
22512    cx.set_state(indoc! {"
22513        def main():
22514            try:
22515                i = 2
22516                if i == 2:
22517                    try:
22518                        i = 3
22519                        ˇ
22520    "});
22521    cx.update_editor(|editor, window, cx| {
22522        editor.handle_input("except:", window, cx);
22523    });
22524    cx.assert_editor_state(indoc! {"
22525        def main():
22526            try:
22527                i = 2
22528                if i == 2:
22529                    try:
22530                        i = 3
22531                    except:ˇ
22532    "});
22533
22534    // test `except` outdents to outer "try" block
22535    cx.set_state(indoc! {"
22536        def main():
22537            try:
22538                i = 2
22539                if i == 2:
22540                    try:
22541                        i = 3
22542                ˇ
22543    "});
22544    cx.update_editor(|editor, window, cx| {
22545        editor.handle_input("except:", window, cx);
22546    });
22547    cx.assert_editor_state(indoc! {"
22548        def main():
22549            try:
22550                i = 2
22551                if i == 2:
22552                    try:
22553                        i = 3
22554            except:ˇ
22555    "});
22556
22557    // test `else` stays at correct indent when typed after `for` block
22558    cx.set_state(indoc! {"
22559        def main():
22560            for i in range(10):
22561                if i == 3:
22562                    break
22563            ˇ
22564    "});
22565    cx.update_editor(|editor, window, cx| {
22566        editor.handle_input("else:", window, cx);
22567    });
22568    cx.assert_editor_state(indoc! {"
22569        def main():
22570            for i in range(10):
22571                if i == 3:
22572                    break
22573            else:ˇ
22574    "});
22575
22576    // test does not outdent on typing after line with square brackets
22577    cx.set_state(indoc! {"
22578        def f() -> list[str]:
22579            ˇ
22580    "});
22581    cx.update_editor(|editor, window, cx| {
22582        editor.handle_input("a", window, cx);
22583    });
22584    cx.assert_editor_state(indoc! {"
22585        def f() -> list[str]:
2258622587    "});
22588
22589    // test does not outdent on typing : after case keyword
22590    cx.set_state(indoc! {"
22591        match 1:
22592            caseˇ
22593    "});
22594    cx.update_editor(|editor, window, cx| {
22595        editor.handle_input(":", window, cx);
22596    });
22597    cx.assert_editor_state(indoc! {"
22598        match 1:
22599            case:ˇ
22600    "});
22601}
22602
22603#[gpui::test]
22604async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22605    init_test(cx, |_| {});
22606    update_test_language_settings(cx, |settings| {
22607        settings.defaults.extend_comment_on_newline = Some(false);
22608    });
22609    let mut cx = EditorTestContext::new(cx).await;
22610    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22611    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22612
22613    // test correct indent after newline on comment
22614    cx.set_state(indoc! {"
22615        # COMMENT:ˇ
22616    "});
22617    cx.update_editor(|editor, window, cx| {
22618        editor.newline(&Newline, window, cx);
22619    });
22620    cx.assert_editor_state(indoc! {"
22621        # COMMENT:
22622        ˇ
22623    "});
22624
22625    // test correct indent after newline in brackets
22626    cx.set_state(indoc! {"
22627        {ˇ}
22628    "});
22629    cx.update_editor(|editor, window, cx| {
22630        editor.newline(&Newline, window, cx);
22631    });
22632    cx.run_until_parked();
22633    cx.assert_editor_state(indoc! {"
22634        {
22635            ˇ
22636        }
22637    "});
22638
22639    cx.set_state(indoc! {"
22640        (ˇ)
22641    "});
22642    cx.update_editor(|editor, window, cx| {
22643        editor.newline(&Newline, window, cx);
22644    });
22645    cx.run_until_parked();
22646    cx.assert_editor_state(indoc! {"
22647        (
22648            ˇ
22649        )
22650    "});
22651
22652    // do not indent after empty lists or dictionaries
22653    cx.set_state(indoc! {"
22654        a = []ˇ
22655    "});
22656    cx.update_editor(|editor, window, cx| {
22657        editor.newline(&Newline, window, cx);
22658    });
22659    cx.run_until_parked();
22660    cx.assert_editor_state(indoc! {"
22661        a = []
22662        ˇ
22663    "});
22664}
22665
22666#[gpui::test]
22667async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22668    init_test(cx, |_| {});
22669
22670    let mut cx = EditorTestContext::new(cx).await;
22671    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22672    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22673
22674    // test cursor move to start of each line on tab
22675    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22676    cx.set_state(indoc! {"
22677        function main() {
22678        ˇ    for item in $items; do
22679        ˇ        while [ -n \"$item\" ]; do
22680        ˇ            if [ \"$value\" -gt 10 ]; then
22681        ˇ                continue
22682        ˇ            elif [ \"$value\" -lt 0 ]; then
22683        ˇ                break
22684        ˇ            else
22685        ˇ                echo \"$item\"
22686        ˇ            fi
22687        ˇ        done
22688        ˇ    done
22689        ˇ}
22690    "});
22691    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22692    cx.assert_editor_state(indoc! {"
22693        function main() {
22694            ˇfor item in $items; do
22695                ˇwhile [ -n \"$item\" ]; do
22696                    ˇif [ \"$value\" -gt 10 ]; then
22697                        ˇcontinue
22698                    ˇelif [ \"$value\" -lt 0 ]; then
22699                        ˇbreak
22700                    ˇelse
22701                        ˇecho \"$item\"
22702                    ˇfi
22703                ˇdone
22704            ˇdone
22705        ˇ}
22706    "});
22707    // test relative indent is preserved when tab
22708    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22709    cx.assert_editor_state(indoc! {"
22710        function main() {
22711                ˇfor item in $items; do
22712                    ˇwhile [ -n \"$item\" ]; do
22713                        ˇif [ \"$value\" -gt 10 ]; then
22714                            ˇcontinue
22715                        ˇelif [ \"$value\" -lt 0 ]; then
22716                            ˇbreak
22717                        ˇelse
22718                            ˇecho \"$item\"
22719                        ˇfi
22720                    ˇdone
22721                ˇdone
22722            ˇ}
22723    "});
22724
22725    // test cursor move to start of each line on tab
22726    // for `case` statement with patterns
22727    cx.set_state(indoc! {"
22728        function handle() {
22729        ˇ    case \"$1\" in
22730        ˇ        start)
22731        ˇ            echo \"a\"
22732        ˇ            ;;
22733        ˇ        stop)
22734        ˇ            echo \"b\"
22735        ˇ            ;;
22736        ˇ        *)
22737        ˇ            echo \"c\"
22738        ˇ            ;;
22739        ˇ    esac
22740        ˇ}
22741    "});
22742    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22743    cx.assert_editor_state(indoc! {"
22744        function handle() {
22745            ˇcase \"$1\" in
22746                ˇstart)
22747                    ˇecho \"a\"
22748                    ˇ;;
22749                ˇstop)
22750                    ˇecho \"b\"
22751                    ˇ;;
22752                ˇ*)
22753                    ˇecho \"c\"
22754                    ˇ;;
22755            ˇesac
22756        ˇ}
22757    "});
22758}
22759
22760#[gpui::test]
22761async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
22762    init_test(cx, |_| {});
22763
22764    let mut cx = EditorTestContext::new(cx).await;
22765    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22766    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22767
22768    // test indents on comment insert
22769    cx.set_state(indoc! {"
22770        function main() {
22771        ˇ    for item in $items; do
22772        ˇ        while [ -n \"$item\" ]; do
22773        ˇ            if [ \"$value\" -gt 10 ]; then
22774        ˇ                continue
22775        ˇ            elif [ \"$value\" -lt 0 ]; then
22776        ˇ                break
22777        ˇ            else
22778        ˇ                echo \"$item\"
22779        ˇ            fi
22780        ˇ        done
22781        ˇ    done
22782        ˇ}
22783    "});
22784    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
22785    cx.assert_editor_state(indoc! {"
22786        function main() {
22787        #ˇ    for item in $items; do
22788        #ˇ        while [ -n \"$item\" ]; do
22789        #ˇ            if [ \"$value\" -gt 10 ]; then
22790        #ˇ                continue
22791        #ˇ            elif [ \"$value\" -lt 0 ]; then
22792        #ˇ                break
22793        #ˇ            else
22794        #ˇ                echo \"$item\"
22795        #ˇ            fi
22796        #ˇ        done
22797        #ˇ    done
22798        #ˇ}
22799    "});
22800}
22801
22802#[gpui::test]
22803async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
22804    init_test(cx, |_| {});
22805
22806    let mut cx = EditorTestContext::new(cx).await;
22807    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22808    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22809
22810    // test `else` auto outdents when typed inside `if` block
22811    cx.set_state(indoc! {"
22812        if [ \"$1\" = \"test\" ]; then
22813            echo \"foo bar\"
22814            ˇ
22815    "});
22816    cx.update_editor(|editor, window, cx| {
22817        editor.handle_input("else", window, cx);
22818    });
22819    cx.assert_editor_state(indoc! {"
22820        if [ \"$1\" = \"test\" ]; then
22821            echo \"foo bar\"
22822        elseˇ
22823    "});
22824
22825    // test `elif` auto outdents when typed inside `if` block
22826    cx.set_state(indoc! {"
22827        if [ \"$1\" = \"test\" ]; then
22828            echo \"foo bar\"
22829            ˇ
22830    "});
22831    cx.update_editor(|editor, window, cx| {
22832        editor.handle_input("elif", window, cx);
22833    });
22834    cx.assert_editor_state(indoc! {"
22835        if [ \"$1\" = \"test\" ]; then
22836            echo \"foo bar\"
22837        elifˇ
22838    "});
22839
22840    // test `fi` auto outdents when typed inside `else` block
22841    cx.set_state(indoc! {"
22842        if [ \"$1\" = \"test\" ]; then
22843            echo \"foo bar\"
22844        else
22845            echo \"bar baz\"
22846            ˇ
22847    "});
22848    cx.update_editor(|editor, window, cx| {
22849        editor.handle_input("fi", window, cx);
22850    });
22851    cx.assert_editor_state(indoc! {"
22852        if [ \"$1\" = \"test\" ]; then
22853            echo \"foo bar\"
22854        else
22855            echo \"bar baz\"
22856        fiˇ
22857    "});
22858
22859    // test `done` auto outdents when typed inside `while` block
22860    cx.set_state(indoc! {"
22861        while read line; do
22862            echo \"$line\"
22863            ˇ
22864    "});
22865    cx.update_editor(|editor, window, cx| {
22866        editor.handle_input("done", window, cx);
22867    });
22868    cx.assert_editor_state(indoc! {"
22869        while read line; do
22870            echo \"$line\"
22871        doneˇ
22872    "});
22873
22874    // test `done` auto outdents when typed inside `for` block
22875    cx.set_state(indoc! {"
22876        for file in *.txt; do
22877            cat \"$file\"
22878            ˇ
22879    "});
22880    cx.update_editor(|editor, window, cx| {
22881        editor.handle_input("done", window, cx);
22882    });
22883    cx.assert_editor_state(indoc! {"
22884        for file in *.txt; do
22885            cat \"$file\"
22886        doneˇ
22887    "});
22888
22889    // test `esac` auto outdents when typed inside `case` block
22890    cx.set_state(indoc! {"
22891        case \"$1\" in
22892            start)
22893                echo \"foo bar\"
22894                ;;
22895            stop)
22896                echo \"bar baz\"
22897                ;;
22898            ˇ
22899    "});
22900    cx.update_editor(|editor, window, cx| {
22901        editor.handle_input("esac", window, cx);
22902    });
22903    cx.assert_editor_state(indoc! {"
22904        case \"$1\" in
22905            start)
22906                echo \"foo bar\"
22907                ;;
22908            stop)
22909                echo \"bar baz\"
22910                ;;
22911        esacˇ
22912    "});
22913
22914    // test `*)` auto outdents when typed inside `case` block
22915    cx.set_state(indoc! {"
22916        case \"$1\" in
22917            start)
22918                echo \"foo bar\"
22919                ;;
22920                ˇ
22921    "});
22922    cx.update_editor(|editor, window, cx| {
22923        editor.handle_input("*)", window, cx);
22924    });
22925    cx.assert_editor_state(indoc! {"
22926        case \"$1\" in
22927            start)
22928                echo \"foo bar\"
22929                ;;
22930            *)ˇ
22931    "});
22932
22933    // test `fi` outdents to correct level with nested if blocks
22934    cx.set_state(indoc! {"
22935        if [ \"$1\" = \"test\" ]; then
22936            echo \"outer if\"
22937            if [ \"$2\" = \"debug\" ]; then
22938                echo \"inner if\"
22939                ˇ
22940    "});
22941    cx.update_editor(|editor, window, cx| {
22942        editor.handle_input("fi", window, cx);
22943    });
22944    cx.assert_editor_state(indoc! {"
22945        if [ \"$1\" = \"test\" ]; then
22946            echo \"outer if\"
22947            if [ \"$2\" = \"debug\" ]; then
22948                echo \"inner if\"
22949            fiˇ
22950    "});
22951}
22952
22953#[gpui::test]
22954async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
22955    init_test(cx, |_| {});
22956    update_test_language_settings(cx, |settings| {
22957        settings.defaults.extend_comment_on_newline = Some(false);
22958    });
22959    let mut cx = EditorTestContext::new(cx).await;
22960    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22961    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22962
22963    // test correct indent after newline on comment
22964    cx.set_state(indoc! {"
22965        # COMMENT:ˇ
22966    "});
22967    cx.update_editor(|editor, window, cx| {
22968        editor.newline(&Newline, window, cx);
22969    });
22970    cx.assert_editor_state(indoc! {"
22971        # COMMENT:
22972        ˇ
22973    "});
22974
22975    // test correct indent after newline after `then`
22976    cx.set_state(indoc! {"
22977
22978        if [ \"$1\" = \"test\" ]; thenˇ
22979    "});
22980    cx.update_editor(|editor, window, cx| {
22981        editor.newline(&Newline, window, cx);
22982    });
22983    cx.run_until_parked();
22984    cx.assert_editor_state(indoc! {"
22985
22986        if [ \"$1\" = \"test\" ]; then
22987            ˇ
22988    "});
22989
22990    // test correct indent after newline after `else`
22991    cx.set_state(indoc! {"
22992        if [ \"$1\" = \"test\" ]; then
22993        elseˇ
22994    "});
22995    cx.update_editor(|editor, window, cx| {
22996        editor.newline(&Newline, window, cx);
22997    });
22998    cx.run_until_parked();
22999    cx.assert_editor_state(indoc! {"
23000        if [ \"$1\" = \"test\" ]; then
23001        else
23002            ˇ
23003    "});
23004
23005    // test correct indent after newline after `elif`
23006    cx.set_state(indoc! {"
23007        if [ \"$1\" = \"test\" ]; then
23008        elifˇ
23009    "});
23010    cx.update_editor(|editor, window, cx| {
23011        editor.newline(&Newline, window, cx);
23012    });
23013    cx.run_until_parked();
23014    cx.assert_editor_state(indoc! {"
23015        if [ \"$1\" = \"test\" ]; then
23016        elif
23017            ˇ
23018    "});
23019
23020    // test correct indent after newline after `do`
23021    cx.set_state(indoc! {"
23022        for file in *.txt; doˇ
23023    "});
23024    cx.update_editor(|editor, window, cx| {
23025        editor.newline(&Newline, window, cx);
23026    });
23027    cx.run_until_parked();
23028    cx.assert_editor_state(indoc! {"
23029        for file in *.txt; do
23030            ˇ
23031    "});
23032
23033    // test correct indent after newline after case pattern
23034    cx.set_state(indoc! {"
23035        case \"$1\" in
23036            start)ˇ
23037    "});
23038    cx.update_editor(|editor, window, cx| {
23039        editor.newline(&Newline, window, cx);
23040    });
23041    cx.run_until_parked();
23042    cx.assert_editor_state(indoc! {"
23043        case \"$1\" in
23044            start)
23045                ˇ
23046    "});
23047
23048    // test correct indent after newline after case pattern
23049    cx.set_state(indoc! {"
23050        case \"$1\" in
23051            start)
23052                ;;
23053            *)ˇ
23054    "});
23055    cx.update_editor(|editor, window, cx| {
23056        editor.newline(&Newline, window, cx);
23057    });
23058    cx.run_until_parked();
23059    cx.assert_editor_state(indoc! {"
23060        case \"$1\" in
23061            start)
23062                ;;
23063            *)
23064                ˇ
23065    "});
23066
23067    // test correct indent after newline after function opening brace
23068    cx.set_state(indoc! {"
23069        function test() {ˇ}
23070    "});
23071    cx.update_editor(|editor, window, cx| {
23072        editor.newline(&Newline, window, cx);
23073    });
23074    cx.run_until_parked();
23075    cx.assert_editor_state(indoc! {"
23076        function test() {
23077            ˇ
23078        }
23079    "});
23080
23081    // test no extra indent after semicolon on same line
23082    cx.set_state(indoc! {"
23083        echo \"test\"23084    "});
23085    cx.update_editor(|editor, window, cx| {
23086        editor.newline(&Newline, window, cx);
23087    });
23088    cx.run_until_parked();
23089    cx.assert_editor_state(indoc! {"
23090        echo \"test\";
23091        ˇ
23092    "});
23093}
23094
23095fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23096    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23097    point..point
23098}
23099
23100#[track_caller]
23101fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23102    let (text, ranges) = marked_text_ranges(marked_text, true);
23103    assert_eq!(editor.text(cx), text);
23104    assert_eq!(
23105        editor.selections.ranges(cx),
23106        ranges,
23107        "Assert selections are {}",
23108        marked_text
23109    );
23110}
23111
23112pub fn handle_signature_help_request(
23113    cx: &mut EditorLspTestContext,
23114    mocked_response: lsp::SignatureHelp,
23115) -> impl Future<Output = ()> + use<> {
23116    let mut request =
23117        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23118            let mocked_response = mocked_response.clone();
23119            async move { Ok(Some(mocked_response)) }
23120        });
23121
23122    async move {
23123        request.next().await;
23124    }
23125}
23126
23127#[track_caller]
23128pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23129    cx.update_editor(|editor, _, _| {
23130        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23131            let entries = menu.entries.borrow();
23132            let entries = entries
23133                .iter()
23134                .map(|entry| entry.string.as_str())
23135                .collect::<Vec<_>>();
23136            assert_eq!(entries, expected);
23137        } else {
23138            panic!("Expected completions menu");
23139        }
23140    });
23141}
23142
23143/// Handle completion request passing a marked string specifying where the completion
23144/// should be triggered from using '|' character, what range should be replaced, and what completions
23145/// should be returned using '<' and '>' to delimit the range.
23146///
23147/// Also see `handle_completion_request_with_insert_and_replace`.
23148#[track_caller]
23149pub fn handle_completion_request(
23150    marked_string: &str,
23151    completions: Vec<&'static str>,
23152    is_incomplete: bool,
23153    counter: Arc<AtomicUsize>,
23154    cx: &mut EditorLspTestContext,
23155) -> impl Future<Output = ()> {
23156    let complete_from_marker: TextRangeMarker = '|'.into();
23157    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23158    let (_, mut marked_ranges) = marked_text_ranges_by(
23159        marked_string,
23160        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23161    );
23162
23163    let complete_from_position =
23164        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23165    let replace_range =
23166        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23167
23168    let mut request =
23169        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23170            let completions = completions.clone();
23171            counter.fetch_add(1, atomic::Ordering::Release);
23172            async move {
23173                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23174                assert_eq!(
23175                    params.text_document_position.position,
23176                    complete_from_position
23177                );
23178                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23179                    is_incomplete: is_incomplete,
23180                    item_defaults: None,
23181                    items: completions
23182                        .iter()
23183                        .map(|completion_text| lsp::CompletionItem {
23184                            label: completion_text.to_string(),
23185                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23186                                range: replace_range,
23187                                new_text: completion_text.to_string(),
23188                            })),
23189                            ..Default::default()
23190                        })
23191                        .collect(),
23192                })))
23193            }
23194        });
23195
23196    async move {
23197        request.next().await;
23198    }
23199}
23200
23201/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23202/// given instead, which also contains an `insert` range.
23203///
23204/// This function uses markers to define ranges:
23205/// - `|` marks the cursor position
23206/// - `<>` marks the replace range
23207/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23208pub fn handle_completion_request_with_insert_and_replace(
23209    cx: &mut EditorLspTestContext,
23210    marked_string: &str,
23211    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23212    counter: Arc<AtomicUsize>,
23213) -> impl Future<Output = ()> {
23214    let complete_from_marker: TextRangeMarker = '|'.into();
23215    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23216    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23217
23218    let (_, mut marked_ranges) = marked_text_ranges_by(
23219        marked_string,
23220        vec![
23221            complete_from_marker.clone(),
23222            replace_range_marker.clone(),
23223            insert_range_marker.clone(),
23224        ],
23225    );
23226
23227    let complete_from_position =
23228        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23229    let replace_range =
23230        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23231
23232    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23233        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23234        _ => lsp::Range {
23235            start: replace_range.start,
23236            end: complete_from_position,
23237        },
23238    };
23239
23240    let mut request =
23241        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23242            let completions = completions.clone();
23243            counter.fetch_add(1, atomic::Ordering::Release);
23244            async move {
23245                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23246                assert_eq!(
23247                    params.text_document_position.position, complete_from_position,
23248                    "marker `|` position doesn't match",
23249                );
23250                Ok(Some(lsp::CompletionResponse::Array(
23251                    completions
23252                        .iter()
23253                        .map(|(label, new_text)| lsp::CompletionItem {
23254                            label: label.to_string(),
23255                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23256                                lsp::InsertReplaceEdit {
23257                                    insert: insert_range,
23258                                    replace: replace_range,
23259                                    new_text: new_text.to_string(),
23260                                },
23261                            )),
23262                            ..Default::default()
23263                        })
23264                        .collect(),
23265                )))
23266            }
23267        });
23268
23269    async move {
23270        request.next().await;
23271    }
23272}
23273
23274fn handle_resolve_completion_request(
23275    cx: &mut EditorLspTestContext,
23276    edits: Option<Vec<(&'static str, &'static str)>>,
23277) -> impl Future<Output = ()> {
23278    let edits = edits.map(|edits| {
23279        edits
23280            .iter()
23281            .map(|(marked_string, new_text)| {
23282                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23283                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23284                lsp::TextEdit::new(replace_range, new_text.to_string())
23285            })
23286            .collect::<Vec<_>>()
23287    });
23288
23289    let mut request =
23290        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23291            let edits = edits.clone();
23292            async move {
23293                Ok(lsp::CompletionItem {
23294                    additional_text_edits: edits,
23295                    ..Default::default()
23296                })
23297            }
23298        });
23299
23300    async move {
23301        request.next().await;
23302    }
23303}
23304
23305pub(crate) fn update_test_language_settings(
23306    cx: &mut TestAppContext,
23307    f: impl Fn(&mut AllLanguageSettingsContent),
23308) {
23309    cx.update(|cx| {
23310        SettingsStore::update_global(cx, |store, cx| {
23311            store.update_user_settings::<AllLanguageSettings>(cx, f);
23312        });
23313    });
23314}
23315
23316pub(crate) fn update_test_project_settings(
23317    cx: &mut TestAppContext,
23318    f: impl Fn(&mut ProjectSettings),
23319) {
23320    cx.update(|cx| {
23321        SettingsStore::update_global(cx, |store, cx| {
23322            store.update_user_settings::<ProjectSettings>(cx, f);
23323        });
23324    });
23325}
23326
23327pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23328    cx.update(|cx| {
23329        assets::Assets.load_test_fonts(cx);
23330        let store = SettingsStore::test(cx);
23331        cx.set_global(store);
23332        theme::init(theme::LoadThemes::JustBase, cx);
23333        release_channel::init(SemanticVersion::default(), cx);
23334        client::init_settings(cx);
23335        language::init(cx);
23336        Project::init_settings(cx);
23337        workspace::init_settings(cx);
23338        crate::init(cx);
23339    });
23340    zlog::init_test();
23341    update_test_language_settings(cx, f);
23342}
23343
23344#[track_caller]
23345fn assert_hunk_revert(
23346    not_reverted_text_with_selections: &str,
23347    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23348    expected_reverted_text_with_selections: &str,
23349    base_text: &str,
23350    cx: &mut EditorLspTestContext,
23351) {
23352    cx.set_state(not_reverted_text_with_selections);
23353    cx.set_head_text(base_text);
23354    cx.executor().run_until_parked();
23355
23356    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23357        let snapshot = editor.snapshot(window, cx);
23358        let reverted_hunk_statuses = snapshot
23359            .buffer_snapshot
23360            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23361            .map(|hunk| hunk.status().kind)
23362            .collect::<Vec<_>>();
23363
23364        editor.git_restore(&Default::default(), window, cx);
23365        reverted_hunk_statuses
23366    });
23367    cx.executor().run_until_parked();
23368    cx.assert_editor_state(expected_reverted_text_with_selections);
23369    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23370}
23371
23372#[gpui::test(iterations = 10)]
23373async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23374    init_test(cx, |_| {});
23375
23376    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23377    let counter = diagnostic_requests.clone();
23378
23379    let fs = FakeFs::new(cx.executor());
23380    fs.insert_tree(
23381        path!("/a"),
23382        json!({
23383            "first.rs": "fn main() { let a = 5; }",
23384            "second.rs": "// Test file",
23385        }),
23386    )
23387    .await;
23388
23389    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23390    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23391    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23392
23393    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23394    language_registry.add(rust_lang());
23395    let mut fake_servers = language_registry.register_fake_lsp(
23396        "Rust",
23397        FakeLspAdapter {
23398            capabilities: lsp::ServerCapabilities {
23399                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23400                    lsp::DiagnosticOptions {
23401                        identifier: None,
23402                        inter_file_dependencies: true,
23403                        workspace_diagnostics: true,
23404                        work_done_progress_options: Default::default(),
23405                    },
23406                )),
23407                ..Default::default()
23408            },
23409            ..Default::default()
23410        },
23411    );
23412
23413    let editor = workspace
23414        .update(cx, |workspace, window, cx| {
23415            workspace.open_abs_path(
23416                PathBuf::from(path!("/a/first.rs")),
23417                OpenOptions::default(),
23418                window,
23419                cx,
23420            )
23421        })
23422        .unwrap()
23423        .await
23424        .unwrap()
23425        .downcast::<Editor>()
23426        .unwrap();
23427    let fake_server = fake_servers.next().await.unwrap();
23428    let server_id = fake_server.server.server_id();
23429    let mut first_request = fake_server
23430        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23431            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23432            let result_id = Some(new_result_id.to_string());
23433            assert_eq!(
23434                params.text_document.uri,
23435                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23436            );
23437            async move {
23438                Ok(lsp::DocumentDiagnosticReportResult::Report(
23439                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23440                        related_documents: None,
23441                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23442                            items: Vec::new(),
23443                            result_id,
23444                        },
23445                    }),
23446                ))
23447            }
23448        });
23449
23450    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23451        project.update(cx, |project, cx| {
23452            let buffer_id = editor
23453                .read(cx)
23454                .buffer()
23455                .read(cx)
23456                .as_singleton()
23457                .expect("created a singleton buffer")
23458                .read(cx)
23459                .remote_id();
23460            let buffer_result_id = project
23461                .lsp_store()
23462                .read(cx)
23463                .result_id(server_id, buffer_id, cx);
23464            assert_eq!(expected, buffer_result_id);
23465        });
23466    };
23467
23468    ensure_result_id(None, cx);
23469    cx.executor().advance_clock(Duration::from_millis(60));
23470    cx.executor().run_until_parked();
23471    assert_eq!(
23472        diagnostic_requests.load(atomic::Ordering::Acquire),
23473        1,
23474        "Opening file should trigger diagnostic request"
23475    );
23476    first_request
23477        .next()
23478        .await
23479        .expect("should have sent the first diagnostics pull request");
23480    ensure_result_id(Some("1".to_string()), cx);
23481
23482    // Editing should trigger diagnostics
23483    editor.update_in(cx, |editor, window, cx| {
23484        editor.handle_input("2", window, cx)
23485    });
23486    cx.executor().advance_clock(Duration::from_millis(60));
23487    cx.executor().run_until_parked();
23488    assert_eq!(
23489        diagnostic_requests.load(atomic::Ordering::Acquire),
23490        2,
23491        "Editing should trigger diagnostic request"
23492    );
23493    ensure_result_id(Some("2".to_string()), cx);
23494
23495    // Moving cursor should not trigger diagnostic request
23496    editor.update_in(cx, |editor, window, cx| {
23497        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23498            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23499        });
23500    });
23501    cx.executor().advance_clock(Duration::from_millis(60));
23502    cx.executor().run_until_parked();
23503    assert_eq!(
23504        diagnostic_requests.load(atomic::Ordering::Acquire),
23505        2,
23506        "Cursor movement should not trigger diagnostic request"
23507    );
23508    ensure_result_id(Some("2".to_string()), cx);
23509    // Multiple rapid edits should be debounced
23510    for _ in 0..5 {
23511        editor.update_in(cx, |editor, window, cx| {
23512            editor.handle_input("x", window, cx)
23513        });
23514    }
23515    cx.executor().advance_clock(Duration::from_millis(60));
23516    cx.executor().run_until_parked();
23517
23518    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23519    assert!(
23520        final_requests <= 4,
23521        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23522    );
23523    ensure_result_id(Some(final_requests.to_string()), cx);
23524}
23525
23526#[gpui::test]
23527async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23528    // Regression test for issue #11671
23529    // Previously, adding a cursor after moving multiple cursors would reset
23530    // the cursor count instead of adding to the existing cursors.
23531    init_test(cx, |_| {});
23532    let mut cx = EditorTestContext::new(cx).await;
23533
23534    // Create a simple buffer with cursor at start
23535    cx.set_state(indoc! {"
23536        ˇaaaa
23537        bbbb
23538        cccc
23539        dddd
23540        eeee
23541        ffff
23542        gggg
23543        hhhh"});
23544
23545    // Add 2 cursors below (so we have 3 total)
23546    cx.update_editor(|editor, window, cx| {
23547        editor.add_selection_below(&Default::default(), window, cx);
23548        editor.add_selection_below(&Default::default(), window, cx);
23549    });
23550
23551    // Verify we have 3 cursors
23552    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23553    assert_eq!(
23554        initial_count, 3,
23555        "Should have 3 cursors after adding 2 below"
23556    );
23557
23558    // Move down one line
23559    cx.update_editor(|editor, window, cx| {
23560        editor.move_down(&MoveDown, window, cx);
23561    });
23562
23563    // Add another cursor below
23564    cx.update_editor(|editor, window, cx| {
23565        editor.add_selection_below(&Default::default(), window, cx);
23566    });
23567
23568    // Should now have 4 cursors (3 original + 1 new)
23569    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23570    assert_eq!(
23571        final_count, 4,
23572        "Should have 4 cursors after moving and adding another"
23573    );
23574}
23575
23576#[gpui::test(iterations = 10)]
23577async fn test_document_colors(cx: &mut TestAppContext) {
23578    let expected_color = Rgba {
23579        r: 0.33,
23580        g: 0.33,
23581        b: 0.33,
23582        a: 0.33,
23583    };
23584
23585    init_test(cx, |_| {});
23586
23587    let fs = FakeFs::new(cx.executor());
23588    fs.insert_tree(
23589        path!("/a"),
23590        json!({
23591            "first.rs": "fn main() { let a = 5; }",
23592        }),
23593    )
23594    .await;
23595
23596    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23597    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23598    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23599
23600    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23601    language_registry.add(rust_lang());
23602    let mut fake_servers = language_registry.register_fake_lsp(
23603        "Rust",
23604        FakeLspAdapter {
23605            capabilities: lsp::ServerCapabilities {
23606                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23607                ..lsp::ServerCapabilities::default()
23608            },
23609            name: "rust-analyzer",
23610            ..FakeLspAdapter::default()
23611        },
23612    );
23613    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23614        "Rust",
23615        FakeLspAdapter {
23616            capabilities: lsp::ServerCapabilities {
23617                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23618                ..lsp::ServerCapabilities::default()
23619            },
23620            name: "not-rust-analyzer",
23621            ..FakeLspAdapter::default()
23622        },
23623    );
23624
23625    let editor = workspace
23626        .update(cx, |workspace, window, cx| {
23627            workspace.open_abs_path(
23628                PathBuf::from(path!("/a/first.rs")),
23629                OpenOptions::default(),
23630                window,
23631                cx,
23632            )
23633        })
23634        .unwrap()
23635        .await
23636        .unwrap()
23637        .downcast::<Editor>()
23638        .unwrap();
23639    let fake_language_server = fake_servers.next().await.unwrap();
23640    let fake_language_server_without_capabilities =
23641        fake_servers_without_capabilities.next().await.unwrap();
23642    let requests_made = Arc::new(AtomicUsize::new(0));
23643    let closure_requests_made = Arc::clone(&requests_made);
23644    let mut color_request_handle = fake_language_server
23645        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23646            let requests_made = Arc::clone(&closure_requests_made);
23647            async move {
23648                assert_eq!(
23649                    params.text_document.uri,
23650                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23651                );
23652                requests_made.fetch_add(1, atomic::Ordering::Release);
23653                Ok(vec![
23654                    lsp::ColorInformation {
23655                        range: lsp::Range {
23656                            start: lsp::Position {
23657                                line: 0,
23658                                character: 0,
23659                            },
23660                            end: lsp::Position {
23661                                line: 0,
23662                                character: 1,
23663                            },
23664                        },
23665                        color: lsp::Color {
23666                            red: 0.33,
23667                            green: 0.33,
23668                            blue: 0.33,
23669                            alpha: 0.33,
23670                        },
23671                    },
23672                    lsp::ColorInformation {
23673                        range: lsp::Range {
23674                            start: lsp::Position {
23675                                line: 0,
23676                                character: 0,
23677                            },
23678                            end: lsp::Position {
23679                                line: 0,
23680                                character: 1,
23681                            },
23682                        },
23683                        color: lsp::Color {
23684                            red: 0.33,
23685                            green: 0.33,
23686                            blue: 0.33,
23687                            alpha: 0.33,
23688                        },
23689                    },
23690                ])
23691            }
23692        });
23693
23694    let _handle = fake_language_server_without_capabilities
23695        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23696            panic!("Should not be called");
23697        });
23698    cx.executor().advance_clock(Duration::from_millis(100));
23699    color_request_handle.next().await.unwrap();
23700    cx.run_until_parked();
23701    assert_eq!(
23702        1,
23703        requests_made.load(atomic::Ordering::Acquire),
23704        "Should query for colors once per editor open"
23705    );
23706    editor.update_in(cx, |editor, _, cx| {
23707        assert_eq!(
23708            vec![expected_color],
23709            extract_color_inlays(editor, cx),
23710            "Should have an initial inlay"
23711        );
23712    });
23713
23714    // opening another file in a split should not influence the LSP query counter
23715    workspace
23716        .update(cx, |workspace, window, cx| {
23717            assert_eq!(
23718                workspace.panes().len(),
23719                1,
23720                "Should have one pane with one editor"
23721            );
23722            workspace.move_item_to_pane_in_direction(
23723                &MoveItemToPaneInDirection {
23724                    direction: SplitDirection::Right,
23725                    focus: false,
23726                    clone: true,
23727                },
23728                window,
23729                cx,
23730            );
23731        })
23732        .unwrap();
23733    cx.run_until_parked();
23734    workspace
23735        .update(cx, |workspace, _, cx| {
23736            let panes = workspace.panes();
23737            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23738            for pane in panes {
23739                let editor = pane
23740                    .read(cx)
23741                    .active_item()
23742                    .and_then(|item| item.downcast::<Editor>())
23743                    .expect("Should have opened an editor in each split");
23744                let editor_file = editor
23745                    .read(cx)
23746                    .buffer()
23747                    .read(cx)
23748                    .as_singleton()
23749                    .expect("test deals with singleton buffers")
23750                    .read(cx)
23751                    .file()
23752                    .expect("test buffese should have a file")
23753                    .path();
23754                assert_eq!(
23755                    editor_file.as_ref(),
23756                    Path::new("first.rs"),
23757                    "Both editors should be opened for the same file"
23758                )
23759            }
23760        })
23761        .unwrap();
23762
23763    cx.executor().advance_clock(Duration::from_millis(500));
23764    let save = editor.update_in(cx, |editor, window, cx| {
23765        editor.move_to_end(&MoveToEnd, window, cx);
23766        editor.handle_input("dirty", window, cx);
23767        editor.save(
23768            SaveOptions {
23769                format: true,
23770                autosave: true,
23771            },
23772            project.clone(),
23773            window,
23774            cx,
23775        )
23776    });
23777    save.await.unwrap();
23778
23779    color_request_handle.next().await.unwrap();
23780    cx.run_until_parked();
23781    assert_eq!(
23782        3,
23783        requests_made.load(atomic::Ordering::Acquire),
23784        "Should query for colors once per save and once per formatting after save"
23785    );
23786
23787    drop(editor);
23788    let close = workspace
23789        .update(cx, |workspace, window, cx| {
23790            workspace.active_pane().update(cx, |pane, cx| {
23791                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23792            })
23793        })
23794        .unwrap();
23795    close.await.unwrap();
23796    let close = workspace
23797        .update(cx, |workspace, window, cx| {
23798            workspace.active_pane().update(cx, |pane, cx| {
23799                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23800            })
23801        })
23802        .unwrap();
23803    close.await.unwrap();
23804    assert_eq!(
23805        3,
23806        requests_made.load(atomic::Ordering::Acquire),
23807        "After saving and closing all editors, no extra requests should be made"
23808    );
23809    workspace
23810        .update(cx, |workspace, _, cx| {
23811            assert!(
23812                workspace.active_item(cx).is_none(),
23813                "Should close all editors"
23814            )
23815        })
23816        .unwrap();
23817
23818    workspace
23819        .update(cx, |workspace, window, cx| {
23820            workspace.active_pane().update(cx, |pane, cx| {
23821                pane.navigate_backward(window, cx);
23822            })
23823        })
23824        .unwrap();
23825    cx.executor().advance_clock(Duration::from_millis(100));
23826    cx.run_until_parked();
23827    let editor = workspace
23828        .update(cx, |workspace, _, cx| {
23829            workspace
23830                .active_item(cx)
23831                .expect("Should have reopened the editor again after navigating back")
23832                .downcast::<Editor>()
23833                .expect("Should be an editor")
23834        })
23835        .unwrap();
23836    color_request_handle.next().await.unwrap();
23837    assert_eq!(
23838        3,
23839        requests_made.load(atomic::Ordering::Acquire),
23840        "Cache should be reused on buffer close and reopen"
23841    );
23842    editor.update(cx, |editor, cx| {
23843        assert_eq!(
23844            vec![expected_color],
23845            extract_color_inlays(editor, cx),
23846            "Should have an initial inlay"
23847        );
23848    });
23849}
23850
23851#[gpui::test]
23852async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23853    init_test(cx, |_| {});
23854    let (editor, cx) = cx.add_window_view(Editor::single_line);
23855    editor.update_in(cx, |editor, window, cx| {
23856        editor.set_text("oops\n\nwow\n", window, cx)
23857    });
23858    cx.run_until_parked();
23859    editor.update(cx, |editor, cx| {
23860        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23861    });
23862    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23863    cx.run_until_parked();
23864    editor.update(cx, |editor, cx| {
23865        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23866    });
23867}
23868
23869#[track_caller]
23870fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23871    editor
23872        .all_inlays(cx)
23873        .into_iter()
23874        .filter_map(|inlay| inlay.get_color())
23875        .map(Rgba::from)
23876        .collect()
23877}