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,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   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, CloseInactiveItems, 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: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 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]
 3084fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3089        let mut editor = build_editor(buffer.clone(), window, cx);
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_ranges([3..4, 11..12, 19..20])
 3092        });
 3093        editor
 3094    });
 3095
 3096    _ = editor.update(cx, |editor, window, cx| {
 3097        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3098        editor.buffer.update(cx, |buffer, cx| {
 3099            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3100            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3101        });
 3102        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3103
 3104        editor.insert("Z", window, cx);
 3105        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3106
 3107        // The selections are moved after the inserted characters
 3108        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3109    });
 3110}
 3111
 3112#[gpui::test]
 3113async fn test_tab(cx: &mut TestAppContext) {
 3114    init_test(cx, |settings| {
 3115        settings.defaults.tab_size = NonZeroU32::new(3)
 3116    });
 3117
 3118    let mut cx = EditorTestContext::new(cx).await;
 3119    cx.set_state(indoc! {"
 3120        ˇabˇc
 3121        ˇ🏀ˇ🏀ˇefg
 3122 3123    "});
 3124    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126           ˇab ˇc
 3127           ˇ🏀  ˇ🏀  ˇefg
 3128        d  ˇ
 3129    "});
 3130
 3131    cx.set_state(indoc! {"
 3132        a
 3133        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3134    "});
 3135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        a
 3138           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3144    init_test(cx, |_| {});
 3145
 3146    let mut cx = EditorTestContext::new(cx).await;
 3147    let language = Arc::new(
 3148        Language::new(
 3149            LanguageConfig::default(),
 3150            Some(tree_sitter_rust::LANGUAGE.into()),
 3151        )
 3152        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3153        .unwrap(),
 3154    );
 3155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3156
 3157    // test when all cursors are not at suggested indent
 3158    // then simply move to their suggested indent location
 3159    cx.set_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162        ˇ
 3163        ˇ    )
 3164        );
 3165    "});
 3166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3167    cx.assert_editor_state(indoc! {"
 3168        const a: B = (
 3169            c(
 3170                ˇ
 3171            ˇ)
 3172        );
 3173    "});
 3174
 3175    // test cursor already at suggested indent not moving when
 3176    // other cursors are yet to reach their suggested indents
 3177    cx.set_state(indoc! {"
 3178        ˇ
 3179        const a: B = (
 3180            c(
 3181                d(
 3182        ˇ
 3183                )
 3184        ˇ
 3185        ˇ    )
 3186        );
 3187    "});
 3188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3189    cx.assert_editor_state(indoc! {"
 3190        ˇ
 3191        const a: B = (
 3192            c(
 3193                d(
 3194                    ˇ
 3195                )
 3196                ˇ
 3197            ˇ)
 3198        );
 3199    "});
 3200    // test when all cursors are at suggested indent then tab is inserted
 3201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203            ˇ
 3204        const a: B = (
 3205            c(
 3206                d(
 3207                        ˇ
 3208                )
 3209                    ˇ
 3210                ˇ)
 3211        );
 3212    "});
 3213
 3214    // test when current indent is less than suggested indent,
 3215    // we adjust line to match suggested indent and move cursor to it
 3216    //
 3217    // when no other cursor is at word boundary, all of them should move
 3218    cx.set_state(indoc! {"
 3219        const a: B = (
 3220            c(
 3221                d(
 3222        ˇ
 3223        ˇ   )
 3224        ˇ   )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        const a: B = (
 3230            c(
 3231                d(
 3232                    ˇ
 3233                ˇ)
 3234            ˇ)
 3235        );
 3236    "});
 3237
 3238    // test when current indent is less than suggested indent,
 3239    // we adjust line to match suggested indent and move cursor to it
 3240    //
 3241    // when some other cursor is at word boundary, it should not move
 3242    cx.set_state(indoc! {"
 3243        const a: B = (
 3244            c(
 3245                d(
 3246        ˇ
 3247        ˇ   )
 3248           ˇ)
 3249        );
 3250    "});
 3251    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3252    cx.assert_editor_state(indoc! {"
 3253        const a: B = (
 3254            c(
 3255                d(
 3256                    ˇ
 3257                ˇ)
 3258            ˇ)
 3259        );
 3260    "});
 3261
 3262    // test when current indent is more than suggested indent,
 3263    // we just move cursor to current indent instead of suggested indent
 3264    //
 3265    // when no other cursor is at word boundary, all of them should move
 3266    cx.set_state(indoc! {"
 3267        const a: B = (
 3268            c(
 3269                d(
 3270        ˇ
 3271        ˇ                )
 3272        ˇ   )
 3273        );
 3274    "});
 3275    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3276    cx.assert_editor_state(indoc! {"
 3277        const a: B = (
 3278            c(
 3279                d(
 3280                    ˇ
 3281                        ˇ)
 3282            ˇ)
 3283        );
 3284    "});
 3285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290                        ˇ
 3291                            ˇ)
 3292                ˇ)
 3293        );
 3294    "});
 3295
 3296    // test when current indent is more than suggested indent,
 3297    // we just move cursor to current indent instead of suggested indent
 3298    //
 3299    // when some other cursor is at word boundary, it doesn't move
 3300    cx.set_state(indoc! {"
 3301        const a: B = (
 3302            c(
 3303                d(
 3304        ˇ
 3305        ˇ                )
 3306            ˇ)
 3307        );
 3308    "});
 3309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3310    cx.assert_editor_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314                    ˇ
 3315                        ˇ)
 3316            ˇ)
 3317        );
 3318    "});
 3319
 3320    // handle auto-indent when there are multiple cursors on the same line
 3321    cx.set_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324        ˇ    ˇ
 3325        ˇ    )
 3326        );
 3327    "});
 3328    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        const a: B = (
 3331            c(
 3332                ˇ
 3333            ˇ)
 3334        );
 3335    "});
 3336}
 3337
 3338#[gpui::test]
 3339async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3340    init_test(cx, |settings| {
 3341        settings.defaults.tab_size = NonZeroU32::new(3)
 3342    });
 3343
 3344    let mut cx = EditorTestContext::new(cx).await;
 3345    cx.set_state(indoc! {"
 3346         ˇ
 3347        \t ˇ
 3348        \t  ˇ
 3349        \t   ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3351    "});
 3352
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355           ˇ
 3356        \t   ˇ
 3357        \t   ˇ
 3358        \t      ˇ
 3359         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3360    "});
 3361}
 3362
 3363#[gpui::test]
 3364async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3365    init_test(cx, |settings| {
 3366        settings.defaults.tab_size = NonZeroU32::new(4)
 3367    });
 3368
 3369    let language = Arc::new(
 3370        Language::new(
 3371            LanguageConfig::default(),
 3372            Some(tree_sitter_rust::LANGUAGE.into()),
 3373        )
 3374        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3375        .unwrap(),
 3376    );
 3377
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3380    cx.set_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383        \t ˇc
 3384            }
 3385        }
 3386    "});
 3387
 3388    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3389    cx.assert_editor_state(indoc! {"
 3390        fn a() {
 3391            if b {
 3392                ˇc
 3393            }
 3394        }
 3395    "});
 3396}
 3397
 3398#[gpui::test]
 3399async fn test_indent_outdent(cx: &mut TestAppContext) {
 3400    init_test(cx, |settings| {
 3401        settings.defaults.tab_size = NonZeroU32::new(4);
 3402    });
 3403
 3404    let mut cx = EditorTestContext::new(cx).await;
 3405
 3406    cx.set_state(indoc! {"
 3407          «oneˇ» «twoˇ»
 3408        three
 3409         four
 3410    "});
 3411    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3412    cx.assert_editor_state(indoc! {"
 3413            «oneˇ» «twoˇ»
 3414        three
 3415         four
 3416    "});
 3417
 3418    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        «oneˇ» «twoˇ»
 3421        three
 3422         four
 3423    "});
 3424
 3425    // select across line ending
 3426    cx.set_state(indoc! {"
 3427        one two
 3428        t«hree
 3429        ˇ» four
 3430    "});
 3431    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3432    cx.assert_editor_state(indoc! {"
 3433        one two
 3434            t«hree
 3435        ˇ» four
 3436    "});
 3437
 3438    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3439    cx.assert_editor_state(indoc! {"
 3440        one two
 3441        t«hree
 3442        ˇ» four
 3443    "});
 3444
 3445    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3446    cx.set_state(indoc! {"
 3447        one two
 3448        ˇthree
 3449            four
 3450    "});
 3451    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3452    cx.assert_editor_state(indoc! {"
 3453        one two
 3454            ˇthree
 3455            four
 3456    "});
 3457
 3458    cx.set_state(indoc! {"
 3459        one two
 3460        ˇ    three
 3461            four
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        one two
 3466        ˇthree
 3467            four
 3468    "});
 3469}
 3470
 3471#[gpui::test]
 3472async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3473    init_test(cx, |settings| {
 3474        settings.defaults.hard_tabs = Some(true);
 3475    });
 3476
 3477    let mut cx = EditorTestContext::new(cx).await;
 3478
 3479    // select two ranges on one line
 3480    cx.set_state(indoc! {"
 3481        «oneˇ» «twoˇ»
 3482        three
 3483        four
 3484    "});
 3485    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3486    cx.assert_editor_state(indoc! {"
 3487        \t«oneˇ» «twoˇ»
 3488        three
 3489        four
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        \t\t«oneˇ» «twoˇ»
 3494        three
 3495        four
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        \t«oneˇ» «twoˇ»
 3500        three
 3501        four
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        «oneˇ» «twoˇ»
 3506        three
 3507        four
 3508    "});
 3509
 3510    // select across a line ending
 3511    cx.set_state(indoc! {"
 3512        one two
 3513        t«hree
 3514        ˇ»four
 3515    "});
 3516    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3517    cx.assert_editor_state(indoc! {"
 3518        one two
 3519        \tt«hree
 3520        ˇ»four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525        \t\tt«hree
 3526        ˇ»four
 3527    "});
 3528    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3529    cx.assert_editor_state(indoc! {"
 3530        one two
 3531        \tt«hree
 3532        ˇ»four
 3533    "});
 3534    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3535    cx.assert_editor_state(indoc! {"
 3536        one two
 3537        t«hree
 3538        ˇ»four
 3539    "});
 3540
 3541    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3542    cx.set_state(indoc! {"
 3543        one two
 3544        ˇthree
 3545        four
 3546    "});
 3547    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3548    cx.assert_editor_state(indoc! {"
 3549        one two
 3550        ˇthree
 3551        four
 3552    "});
 3553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3554    cx.assert_editor_state(indoc! {"
 3555        one two
 3556        \tˇthree
 3557        four
 3558    "});
 3559    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3560    cx.assert_editor_state(indoc! {"
 3561        one two
 3562        ˇthree
 3563        four
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3569    init_test(cx, |settings| {
 3570        settings.languages.0.extend([
 3571            (
 3572                "TOML".into(),
 3573                LanguageSettingsContent {
 3574                    tab_size: NonZeroU32::new(2),
 3575                    ..Default::default()
 3576                },
 3577            ),
 3578            (
 3579                "Rust".into(),
 3580                LanguageSettingsContent {
 3581                    tab_size: NonZeroU32::new(4),
 3582                    ..Default::default()
 3583                },
 3584            ),
 3585        ]);
 3586    });
 3587
 3588    let toml_language = Arc::new(Language::new(
 3589        LanguageConfig {
 3590            name: "TOML".into(),
 3591            ..Default::default()
 3592        },
 3593        None,
 3594    ));
 3595    let rust_language = Arc::new(Language::new(
 3596        LanguageConfig {
 3597            name: "Rust".into(),
 3598            ..Default::default()
 3599        },
 3600        None,
 3601    ));
 3602
 3603    let toml_buffer =
 3604        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3605    let rust_buffer =
 3606        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3607    let multibuffer = cx.new(|cx| {
 3608        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3609        multibuffer.push_excerpts(
 3610            toml_buffer.clone(),
 3611            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3612            cx,
 3613        );
 3614        multibuffer.push_excerpts(
 3615            rust_buffer.clone(),
 3616            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3617            cx,
 3618        );
 3619        multibuffer
 3620    });
 3621
 3622    cx.add_window(|window, cx| {
 3623        let mut editor = build_editor(multibuffer, window, cx);
 3624
 3625        assert_eq!(
 3626            editor.text(cx),
 3627            indoc! {"
 3628                a = 1
 3629                b = 2
 3630
 3631                const c: usize = 3;
 3632            "}
 3633        );
 3634
 3635        select_ranges(
 3636            &mut editor,
 3637            indoc! {"
 3638                «aˇ» = 1
 3639                b = 2
 3640
 3641                «const c:ˇ» usize = 3;
 3642            "},
 3643            window,
 3644            cx,
 3645        );
 3646
 3647        editor.tab(&Tab, window, cx);
 3648        assert_text_with_selections(
 3649            &mut editor,
 3650            indoc! {"
 3651                  «aˇ» = 1
 3652                b = 2
 3653
 3654                    «const c:ˇ» usize = 3;
 3655            "},
 3656            cx,
 3657        );
 3658        editor.backtab(&Backtab, window, cx);
 3659        assert_text_with_selections(
 3660            &mut editor,
 3661            indoc! {"
 3662                «aˇ» = 1
 3663                b = 2
 3664
 3665                «const c:ˇ» usize = 3;
 3666            "},
 3667            cx,
 3668        );
 3669
 3670        editor
 3671    });
 3672}
 3673
 3674#[gpui::test]
 3675async fn test_backspace(cx: &mut TestAppContext) {
 3676    init_test(cx, |_| {});
 3677
 3678    let mut cx = EditorTestContext::new(cx).await;
 3679
 3680    // Basic backspace
 3681    cx.set_state(indoc! {"
 3682        onˇe two three
 3683        fou«rˇ» five six
 3684        seven «ˇeight nine
 3685        »ten
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        oˇe two three
 3690        fouˇ five six
 3691        seven ˇten
 3692    "});
 3693
 3694    // Test backspace inside and around indents
 3695    cx.set_state(indoc! {"
 3696        zero
 3697            ˇone
 3698                ˇtwo
 3699            ˇ ˇ ˇ  three
 3700        ˇ  ˇ  four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        zero
 3705        ˇone
 3706            ˇtwo
 3707        ˇ  threeˇ  four
 3708    "});
 3709}
 3710
 3711#[gpui::test]
 3712async fn test_delete(cx: &mut TestAppContext) {
 3713    init_test(cx, |_| {});
 3714
 3715    let mut cx = EditorTestContext::new(cx).await;
 3716    cx.set_state(indoc! {"
 3717        onˇe two three
 3718        fou«rˇ» five six
 3719        seven «ˇeight nine
 3720        »ten
 3721    "});
 3722    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3723    cx.assert_editor_state(indoc! {"
 3724        onˇ two three
 3725        fouˇ five six
 3726        seven ˇten
 3727    "});
 3728}
 3729
 3730#[gpui::test]
 3731fn test_delete_line(cx: &mut TestAppContext) {
 3732    init_test(cx, |_| {});
 3733
 3734    let editor = cx.add_window(|window, cx| {
 3735        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3736        build_editor(buffer, window, cx)
 3737    });
 3738    _ = editor.update(cx, |editor, window, cx| {
 3739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3740            s.select_display_ranges([
 3741                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3742                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3743                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3744            ])
 3745        });
 3746        editor.delete_line(&DeleteLine, window, cx);
 3747        assert_eq!(editor.display_text(cx), "ghi");
 3748        assert_eq!(
 3749            editor.selections.display_ranges(cx),
 3750            vec![
 3751                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3752                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3753            ]
 3754        );
 3755    });
 3756
 3757    let editor = cx.add_window(|window, cx| {
 3758        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3759        build_editor(buffer, window, cx)
 3760    });
 3761    _ = editor.update(cx, |editor, window, cx| {
 3762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3763            s.select_display_ranges([
 3764                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3765            ])
 3766        });
 3767        editor.delete_line(&DeleteLine, window, cx);
 3768        assert_eq!(editor.display_text(cx), "ghi\n");
 3769        assert_eq!(
 3770            editor.selections.display_ranges(cx),
 3771            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3772        );
 3773    });
 3774}
 3775
 3776#[gpui::test]
 3777fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3778    init_test(cx, |_| {});
 3779
 3780    cx.add_window(|window, cx| {
 3781        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3782        let mut editor = build_editor(buffer.clone(), window, cx);
 3783        let buffer = buffer.read(cx).as_singleton().unwrap();
 3784
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 0)..Point::new(0, 0)]
 3788        );
 3789
 3790        // When on single line, replace newline at end by space
 3791        editor.join_lines(&JoinLines, window, cx);
 3792        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3793        assert_eq!(
 3794            editor.selections.ranges::<Point>(cx),
 3795            &[Point::new(0, 3)..Point::new(0, 3)]
 3796        );
 3797
 3798        // When multiple lines are selected, remove newlines that are spanned by the selection
 3799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3800            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3801        });
 3802        editor.join_lines(&JoinLines, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            &[Point::new(0, 11)..Point::new(0, 11)]
 3807        );
 3808
 3809        // Undo should be transactional
 3810        editor.undo(&Undo, window, cx);
 3811        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3812        assert_eq!(
 3813            editor.selections.ranges::<Point>(cx),
 3814            &[Point::new(0, 5)..Point::new(2, 2)]
 3815        );
 3816
 3817        // When joining an empty line don't insert a space
 3818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3819            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3820        });
 3821        editor.join_lines(&JoinLines, window, cx);
 3822        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3823        assert_eq!(
 3824            editor.selections.ranges::<Point>(cx),
 3825            [Point::new(2, 3)..Point::new(2, 3)]
 3826        );
 3827
 3828        // We can remove trailing newlines
 3829        editor.join_lines(&JoinLines, window, cx);
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3831        assert_eq!(
 3832            editor.selections.ranges::<Point>(cx),
 3833            [Point::new(2, 3)..Point::new(2, 3)]
 3834        );
 3835
 3836        // We don't blow up on the last line
 3837        editor.join_lines(&JoinLines, window, cx);
 3838        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3839        assert_eq!(
 3840            editor.selections.ranges::<Point>(cx),
 3841            [Point::new(2, 3)..Point::new(2, 3)]
 3842        );
 3843
 3844        // reset to test indentation
 3845        editor.buffer.update(cx, |buffer, cx| {
 3846            buffer.edit(
 3847                [
 3848                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3849                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3850                ],
 3851                None,
 3852                cx,
 3853            )
 3854        });
 3855
 3856        // We remove any leading spaces
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3859            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3860        });
 3861        editor.join_lines(&JoinLines, window, cx);
 3862        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3863
 3864        // We don't insert a space for a line containing only spaces
 3865        editor.join_lines(&JoinLines, window, cx);
 3866        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3867
 3868        // We ignore any leading tabs
 3869        editor.join_lines(&JoinLines, window, cx);
 3870        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3871
 3872        editor
 3873    });
 3874}
 3875
 3876#[gpui::test]
 3877fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3878    init_test(cx, |_| {});
 3879
 3880    cx.add_window(|window, cx| {
 3881        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3882        let mut editor = build_editor(buffer.clone(), window, cx);
 3883        let buffer = buffer.read(cx).as_singleton().unwrap();
 3884
 3885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3886            s.select_ranges([
 3887                Point::new(0, 2)..Point::new(1, 1),
 3888                Point::new(1, 2)..Point::new(1, 2),
 3889                Point::new(3, 1)..Point::new(3, 2),
 3890            ])
 3891        });
 3892
 3893        editor.join_lines(&JoinLines, window, cx);
 3894        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3895
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            [
 3899                Point::new(0, 7)..Point::new(0, 7),
 3900                Point::new(1, 3)..Point::new(1, 3)
 3901            ]
 3902        );
 3903        editor
 3904    });
 3905}
 3906
 3907#[gpui::test]
 3908async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3909    init_test(cx, |_| {});
 3910
 3911    let mut cx = EditorTestContext::new(cx).await;
 3912
 3913    let diff_base = r#"
 3914        Line 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919    .unindent();
 3920
 3921    cx.set_state(
 3922        &r#"
 3923        ˇLine 0
 3924        Line 1
 3925        Line 2
 3926        Line 3
 3927        "#
 3928        .unindent(),
 3929    );
 3930
 3931    cx.set_head_text(&diff_base);
 3932    executor.run_until_parked();
 3933
 3934    // Join lines
 3935    cx.update_editor(|editor, window, cx| {
 3936        editor.join_lines(&JoinLines, window, cx);
 3937    });
 3938    executor.run_until_parked();
 3939
 3940    cx.assert_editor_state(
 3941        &r#"
 3942        Line 0ˇ Line 1
 3943        Line 2
 3944        Line 3
 3945        "#
 3946        .unindent(),
 3947    );
 3948    // Join again
 3949    cx.update_editor(|editor, window, cx| {
 3950        editor.join_lines(&JoinLines, window, cx);
 3951    });
 3952    executor.run_until_parked();
 3953
 3954    cx.assert_editor_state(
 3955        &r#"
 3956        Line 0 Line 1ˇ Line 2
 3957        Line 3
 3958        "#
 3959        .unindent(),
 3960    );
 3961}
 3962
 3963#[gpui::test]
 3964async fn test_custom_newlines_cause_no_false_positive_diffs(
 3965    executor: BackgroundExecutor,
 3966    cx: &mut TestAppContext,
 3967) {
 3968    init_test(cx, |_| {});
 3969    let mut cx = EditorTestContext::new(cx).await;
 3970    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3971    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3972    executor.run_until_parked();
 3973
 3974    cx.update_editor(|editor, window, cx| {
 3975        let snapshot = editor.snapshot(window, cx);
 3976        assert_eq!(
 3977            snapshot
 3978                .buffer_snapshot
 3979                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3980                .collect::<Vec<_>>(),
 3981            Vec::new(),
 3982            "Should not have any diffs for files with custom newlines"
 3983        );
 3984    });
 3985}
 3986
 3987#[gpui::test]
 3988async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 3989    init_test(cx, |_| {});
 3990
 3991    let mut cx = EditorTestContext::new(cx).await;
 3992
 3993    // Test sort_lines_case_insensitive()
 3994    cx.set_state(indoc! {"
 3995        «z
 3996        y
 3997        x
 3998        Z
 3999        Y
 4000        Xˇ»
 4001    "});
 4002    cx.update_editor(|e, window, cx| {
 4003        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4004    });
 4005    cx.assert_editor_state(indoc! {"
 4006        «x
 4007        X
 4008        y
 4009        Y
 4010        z
 4011        Zˇ»
 4012    "});
 4013
 4014    // Test reverse_lines()
 4015    cx.set_state(indoc! {"
 4016        «5
 4017        4
 4018        3
 4019        2
 4020        1ˇ»
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        «1
 4025        2
 4026        3
 4027        4
 4028        5ˇ»
 4029    "});
 4030
 4031    // Skip testing shuffle_line()
 4032
 4033    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4034    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4035
 4036    // Don't manipulate when cursor is on single line, but expand the selection
 4037    cx.set_state(indoc! {"
 4038        ddˇdd
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043    cx.update_editor(|e, window, cx| {
 4044        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4045    });
 4046    cx.assert_editor_state(indoc! {"
 4047        «ddddˇ»
 4048        ccc
 4049        bb
 4050        a
 4051    "});
 4052
 4053    // Basic manipulate case
 4054    // Start selection moves to column 0
 4055    // End of selection shrinks to fit shorter line
 4056    cx.set_state(indoc! {"
 4057        dd«d
 4058        ccc
 4059        bb
 4060        aaaaaˇ»
 4061    "});
 4062    cx.update_editor(|e, window, cx| {
 4063        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4064    });
 4065    cx.assert_editor_state(indoc! {"
 4066        «aaaaa
 4067        bb
 4068        ccc
 4069        dddˇ»
 4070    "});
 4071
 4072    // Manipulate case with newlines
 4073    cx.set_state(indoc! {"
 4074        dd«d
 4075        ccc
 4076
 4077        bb
 4078        aaaaa
 4079
 4080        ˇ»
 4081    "});
 4082    cx.update_editor(|e, window, cx| {
 4083        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4084    });
 4085    cx.assert_editor_state(indoc! {"
 4086        «
 4087
 4088        aaaaa
 4089        bb
 4090        ccc
 4091        dddˇ»
 4092
 4093    "});
 4094
 4095    // Adding new line
 4096    cx.set_state(indoc! {"
 4097        aa«a
 4098        bbˇ»b
 4099    "});
 4100    cx.update_editor(|e, window, cx| {
 4101        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4102    });
 4103    cx.assert_editor_state(indoc! {"
 4104        «aaa
 4105        bbb
 4106        added_lineˇ»
 4107    "});
 4108
 4109    // Removing line
 4110    cx.set_state(indoc! {"
 4111        aa«a
 4112        bbbˇ»
 4113    "});
 4114    cx.update_editor(|e, window, cx| {
 4115        e.manipulate_immutable_lines(window, cx, |lines| {
 4116            lines.pop();
 4117        })
 4118    });
 4119    cx.assert_editor_state(indoc! {"
 4120        «aaaˇ»
 4121    "});
 4122
 4123    // Removing all lines
 4124    cx.set_state(indoc! {"
 4125        aa«a
 4126        bbbˇ»
 4127    "});
 4128    cx.update_editor(|e, window, cx| {
 4129        e.manipulate_immutable_lines(window, cx, |lines| {
 4130            lines.drain(..);
 4131        })
 4132    });
 4133    cx.assert_editor_state(indoc! {"
 4134        ˇ
 4135    "});
 4136}
 4137
 4138#[gpui::test]
 4139async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4140    init_test(cx, |_| {});
 4141
 4142    let mut cx = EditorTestContext::new(cx).await;
 4143
 4144    // Consider continuous selection as single selection
 4145    cx.set_state(indoc! {"
 4146        Aaa«aa
 4147        cˇ»c«c
 4148        bb
 4149        aaaˇ»aa
 4150    "});
 4151    cx.update_editor(|e, window, cx| {
 4152        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4153    });
 4154    cx.assert_editor_state(indoc! {"
 4155        «Aaaaa
 4156        ccc
 4157        bb
 4158        aaaaaˇ»
 4159    "});
 4160
 4161    cx.set_state(indoc! {"
 4162        Aaa«aa
 4163        cˇ»c«c
 4164        bb
 4165        aaaˇ»aa
 4166    "});
 4167    cx.update_editor(|e, window, cx| {
 4168        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4169    });
 4170    cx.assert_editor_state(indoc! {"
 4171        «Aaaaa
 4172        ccc
 4173        bbˇ»
 4174    "});
 4175
 4176    // Consider non continuous selection as distinct dedup operations
 4177    cx.set_state(indoc! {"
 4178        «aaaaa
 4179        bb
 4180        aaaaa
 4181        aaaaaˇ»
 4182
 4183        aaa«aaˇ»
 4184    "});
 4185    cx.update_editor(|e, window, cx| {
 4186        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4187    });
 4188    cx.assert_editor_state(indoc! {"
 4189        «aaaaa
 4190        bbˇ»
 4191
 4192        «aaaaaˇ»
 4193    "});
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4198    init_test(cx, |_| {});
 4199
 4200    let mut cx = EditorTestContext::new(cx).await;
 4201
 4202    cx.set_state(indoc! {"
 4203        «Aaa
 4204        aAa
 4205        Aaaˇ»
 4206    "});
 4207    cx.update_editor(|e, window, cx| {
 4208        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4209    });
 4210    cx.assert_editor_state(indoc! {"
 4211        «Aaa
 4212        aAaˇ»
 4213    "});
 4214
 4215    cx.set_state(indoc! {"
 4216        «Aaa
 4217        aAa
 4218        aaAˇ»
 4219    "});
 4220    cx.update_editor(|e, window, cx| {
 4221        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4222    });
 4223    cx.assert_editor_state(indoc! {"
 4224        «Aaaˇ»
 4225    "});
 4226}
 4227
 4228#[gpui::test]
 4229async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4230    init_test(cx, |_| {});
 4231
 4232    let mut cx = EditorTestContext::new(cx).await;
 4233
 4234    // Manipulate with multiple selections on a single line
 4235    cx.set_state(indoc! {"
 4236        dd«dd
 4237        cˇ»c«c
 4238        bb
 4239        aaaˇ»aa
 4240    "});
 4241    cx.update_editor(|e, window, cx| {
 4242        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4243    });
 4244    cx.assert_editor_state(indoc! {"
 4245        «aaaaa
 4246        bb
 4247        ccc
 4248        ddddˇ»
 4249    "});
 4250
 4251    // Manipulate with multiple disjoin selections
 4252    cx.set_state(indoc! {"
 4253 4254        4
 4255        3
 4256        2
 4257        1ˇ»
 4258
 4259        dd«dd
 4260        ccc
 4261        bb
 4262        aaaˇ»aa
 4263    "});
 4264    cx.update_editor(|e, window, cx| {
 4265        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4266    });
 4267    cx.assert_editor_state(indoc! {"
 4268        «1
 4269        2
 4270        3
 4271        4
 4272        5ˇ»
 4273
 4274        «aaaaa
 4275        bb
 4276        ccc
 4277        ddddˇ»
 4278    "});
 4279
 4280    // Adding lines on each selection
 4281    cx.set_state(indoc! {"
 4282 4283        1ˇ»
 4284
 4285        bb«bb
 4286        aaaˇ»aa
 4287    "});
 4288    cx.update_editor(|e, window, cx| {
 4289        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4290    });
 4291    cx.assert_editor_state(indoc! {"
 4292        «2
 4293        1
 4294        added lineˇ»
 4295
 4296        «bbbb
 4297        aaaaa
 4298        added lineˇ»
 4299    "});
 4300
 4301    // Removing lines on each selection
 4302    cx.set_state(indoc! {"
 4303 4304        1ˇ»
 4305
 4306        bb«bb
 4307        aaaˇ»aa
 4308    "});
 4309    cx.update_editor(|e, window, cx| {
 4310        e.manipulate_immutable_lines(window, cx, |lines| {
 4311            lines.pop();
 4312        })
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «2ˇ»
 4316
 4317        «bbbbˇ»
 4318    "});
 4319}
 4320
 4321#[gpui::test]
 4322async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4323    init_test(cx, |settings| {
 4324        settings.defaults.tab_size = NonZeroU32::new(3)
 4325    });
 4326
 4327    let mut cx = EditorTestContext::new(cx).await;
 4328
 4329    // MULTI SELECTION
 4330    // Ln.1 "«" tests empty lines
 4331    // Ln.9 tests just leading whitespace
 4332    cx.set_state(indoc! {"
 4333        «
 4334        abc                 // No indentationˇ»
 4335        «\tabc              // 1 tabˇ»
 4336        \t\tabc «      ˇ»   // 2 tabs
 4337        \t ab«c             // Tab followed by space
 4338         \tabc              // Space followed by tab (3 spaces should be the result)
 4339        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4340           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4341        \t
 4342        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4343    "});
 4344    cx.update_editor(|e, window, cx| {
 4345        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4346    });
 4347    cx.assert_editor_state(
 4348        indoc! {"
 4349            «
 4350            abc                 // No indentation
 4351               abc              // 1 tab
 4352                  abc          // 2 tabs
 4353                abc             // Tab followed by space
 4354               abc              // Space followed by tab (3 spaces should be the result)
 4355                           abc   // Mixed indentation (tab conversion depends on the column)
 4356               abc         // Already space indented
 4357               ·
 4358               abc\tdef          // Only the leading tab is manipulatedˇ»
 4359        "}
 4360        .replace("·", "")
 4361        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4362    );
 4363
 4364    // Test on just a few lines, the others should remain unchanged
 4365    // Only lines (3, 5, 10, 11) should change
 4366    cx.set_state(
 4367        indoc! {"
 4368            ·
 4369            abc                 // No indentation
 4370            \tabcˇ               // 1 tab
 4371            \t\tabc             // 2 tabs
 4372            \t abcˇ              // Tab followed by space
 4373             \tabc              // Space followed by tab (3 spaces should be the result)
 4374            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4375               abc              // Already space indented
 4376            «\t
 4377            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4378        "}
 4379        .replace("·", "")
 4380        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4381    );
 4382    cx.update_editor(|e, window, cx| {
 4383        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4384    });
 4385    cx.assert_editor_state(
 4386        indoc! {"
 4387            ·
 4388            abc                 // No indentation
 4389            «   abc               // 1 tabˇ»
 4390            \t\tabc             // 2 tabs
 4391            «    abc              // Tab followed by spaceˇ»
 4392             \tabc              // Space followed by tab (3 spaces should be the result)
 4393            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4394               abc              // Already space indented
 4395            «   ·
 4396               abc\tdef          // Only the leading tab is manipulatedˇ»
 4397        "}
 4398        .replace("·", "")
 4399        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4400    );
 4401
 4402    // SINGLE SELECTION
 4403    // Ln.1 "«" tests empty lines
 4404    // Ln.9 tests just leading whitespace
 4405    cx.set_state(indoc! {"
 4406        «
 4407        abc                 // No indentation
 4408        \tabc               // 1 tab
 4409        \t\tabc             // 2 tabs
 4410        \t abc              // Tab followed by space
 4411         \tabc              // Space followed by tab (3 spaces should be the result)
 4412        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4413           abc              // Already space indented
 4414        \t
 4415        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4419    });
 4420    cx.assert_editor_state(
 4421        indoc! {"
 4422            «
 4423            abc                 // No indentation
 4424               abc               // 1 tab
 4425                  abc             // 2 tabs
 4426                abc              // Tab followed by space
 4427               abc              // Space followed by tab (3 spaces should be the result)
 4428                           abc   // Mixed indentation (tab conversion depends on the column)
 4429               abc              // Already space indented
 4430               ·
 4431               abc\tdef          // Only the leading tab is manipulatedˇ»
 4432        "}
 4433        .replace("·", "")
 4434        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4435    );
 4436}
 4437
 4438#[gpui::test]
 4439async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4440    init_test(cx, |settings| {
 4441        settings.defaults.tab_size = NonZeroU32::new(3)
 4442    });
 4443
 4444    let mut cx = EditorTestContext::new(cx).await;
 4445
 4446    // MULTI SELECTION
 4447    // Ln.1 "«" tests empty lines
 4448    // Ln.11 tests just leading whitespace
 4449    cx.set_state(indoc! {"
 4450        «
 4451        abˇ»ˇc                 // No indentation
 4452         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4453          abc  «             // 2 spaces (< 3 so dont convert)
 4454           abc              // 3 spaces (convert)
 4455             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4456        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4457        «\t abc              // Tab followed by space
 4458         \tabc              // Space followed by tab (should be consumed due to tab)
 4459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4460           \tˇ»  «\t
 4461           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4462    "});
 4463    cx.update_editor(|e, window, cx| {
 4464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4465    });
 4466    cx.assert_editor_state(indoc! {"
 4467        «
 4468        abc                 // No indentation
 4469         abc                // 1 space (< 3 so dont convert)
 4470          abc               // 2 spaces (< 3 so dont convert)
 4471        \tabc              // 3 spaces (convert)
 4472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4473        \t\t\tabc           // Already tab indented
 4474        \t abc              // Tab followed by space
 4475        \tabc              // Space followed by tab (should be consumed due to tab)
 4476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4477        \t\t\t
 4478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4479    "});
 4480
 4481    // Test on just a few lines, the other should remain unchanged
 4482    // Only lines (4, 8, 11, 12) should change
 4483    cx.set_state(
 4484        indoc! {"
 4485            ·
 4486            abc                 // No indentation
 4487             abc                // 1 space (< 3 so dont convert)
 4488              abc               // 2 spaces (< 3 so dont convert)
 4489            «   abc              // 3 spaces (convert)ˇ»
 4490                 abc            // 5 spaces (1 tab + 2 spaces)
 4491            \t\t\tabc           // Already tab indented
 4492            \t abc              // Tab followed by space
 4493             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4494               \t\t  \tabc      // Mixed indentation
 4495            \t \t  \t   \tabc   // Mixed indentation
 4496               \t  \tˇ
 4497            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4498        "}
 4499        .replace("·", "")
 4500        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4501    );
 4502    cx.update_editor(|e, window, cx| {
 4503        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4504    });
 4505    cx.assert_editor_state(
 4506        indoc! {"
 4507            ·
 4508            abc                 // No indentation
 4509             abc                // 1 space (< 3 so dont convert)
 4510              abc               // 2 spaces (< 3 so dont convert)
 4511            «\tabc              // 3 spaces (convert)ˇ»
 4512                 abc            // 5 spaces (1 tab + 2 spaces)
 4513            \t\t\tabc           // Already tab indented
 4514            \t abc              // Tab followed by space
 4515            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4516               \t\t  \tabc      // Mixed indentation
 4517            \t \t  \t   \tabc   // Mixed indentation
 4518            «\t\t\t
 4519            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4520        "}
 4521        .replace("·", "")
 4522        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4523    );
 4524
 4525    // SINGLE SELECTION
 4526    // Ln.1 "«" tests empty lines
 4527    // Ln.11 tests just leading whitespace
 4528    cx.set_state(indoc! {"
 4529        «
 4530        abc                 // No indentation
 4531         abc                // 1 space (< 3 so dont convert)
 4532          abc               // 2 spaces (< 3 so dont convert)
 4533           abc              // 3 spaces (convert)
 4534             abc            // 5 spaces (1 tab + 2 spaces)
 4535        \t\t\tabc           // Already tab indented
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (should be consumed due to tab)
 4538        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4539           \t  \t
 4540           abc   \t         // Only the leading spaces should be convertedˇ»
 4541    "});
 4542    cx.update_editor(|e, window, cx| {
 4543        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4544    });
 4545    cx.assert_editor_state(indoc! {"
 4546        «
 4547        abc                 // No indentation
 4548         abc                // 1 space (< 3 so dont convert)
 4549          abc               // 2 spaces (< 3 so dont convert)
 4550        \tabc              // 3 spaces (convert)
 4551        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4552        \t\t\tabc           // Already tab indented
 4553        \t abc              // Tab followed by space
 4554        \tabc              // Space followed by tab (should be consumed due to tab)
 4555        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4556        \t\t\t
 4557        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4558    "});
 4559}
 4560
 4561#[gpui::test]
 4562async fn test_toggle_case(cx: &mut TestAppContext) {
 4563    init_test(cx, |_| {});
 4564
 4565    let mut cx = EditorTestContext::new(cx).await;
 4566
 4567    // If all lower case -> upper case
 4568    cx.set_state(indoc! {"
 4569        «hello worldˇ»
 4570    "});
 4571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4572    cx.assert_editor_state(indoc! {"
 4573        «HELLO WORLDˇ»
 4574    "});
 4575
 4576    // If all upper case -> lower case
 4577    cx.set_state(indoc! {"
 4578        «HELLO WORLDˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4581    cx.assert_editor_state(indoc! {"
 4582        «hello worldˇ»
 4583    "});
 4584
 4585    // If any upper case characters are identified -> lower case
 4586    // This matches JetBrains IDEs
 4587    cx.set_state(indoc! {"
 4588        «hEllo worldˇ»
 4589    "});
 4590    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4591    cx.assert_editor_state(indoc! {"
 4592        «hello worldˇ»
 4593    "});
 4594}
 4595
 4596#[gpui::test]
 4597async fn test_manipulate_text(cx: &mut TestAppContext) {
 4598    init_test(cx, |_| {});
 4599
 4600    let mut cx = EditorTestContext::new(cx).await;
 4601
 4602    // Test convert_to_upper_case()
 4603    cx.set_state(indoc! {"
 4604        «hello worldˇ»
 4605    "});
 4606    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4607    cx.assert_editor_state(indoc! {"
 4608        «HELLO WORLDˇ»
 4609    "});
 4610
 4611    // Test convert_to_lower_case()
 4612    cx.set_state(indoc! {"
 4613        «HELLO WORLDˇ»
 4614    "});
 4615    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4616    cx.assert_editor_state(indoc! {"
 4617        «hello worldˇ»
 4618    "});
 4619
 4620    // Test multiple line, single selection case
 4621    cx.set_state(indoc! {"
 4622        «The quick brown
 4623        fox jumps over
 4624        the lazy dogˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4627    cx.assert_editor_state(indoc! {"
 4628        «The Quick Brown
 4629        Fox Jumps Over
 4630        The Lazy Dogˇ»
 4631    "});
 4632
 4633    // Test multiple line, single selection case
 4634    cx.set_state(indoc! {"
 4635        «The quick brown
 4636        fox jumps over
 4637        the lazy dogˇ»
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «TheQuickBrown
 4644        FoxJumpsOver
 4645        TheLazyDogˇ»
 4646    "});
 4647
 4648    // From here on out, test more complex cases of manipulate_text()
 4649
 4650    // Test no selection case - should affect words cursors are in
 4651    // Cursor at beginning, middle, and end of word
 4652    cx.set_state(indoc! {"
 4653        ˇhello big beauˇtiful worldˇ
 4654    "});
 4655    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4656    cx.assert_editor_state(indoc! {"
 4657        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4658    "});
 4659
 4660    // Test multiple selections on a single line and across multiple lines
 4661    cx.set_state(indoc! {"
 4662        «Theˇ» quick «brown
 4663        foxˇ» jumps «overˇ»
 4664        the «lazyˇ» dog
 4665    "});
 4666    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4667    cx.assert_editor_state(indoc! {"
 4668        «THEˇ» quick «BROWN
 4669        FOXˇ» jumps «OVERˇ»
 4670        the «LAZYˇ» dog
 4671    "});
 4672
 4673    // Test case where text length grows
 4674    cx.set_state(indoc! {"
 4675        «tschüߡ»
 4676    "});
 4677    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4678    cx.assert_editor_state(indoc! {"
 4679        «TSCHÜSSˇ»
 4680    "});
 4681
 4682    // Test to make sure we don't crash when text shrinks
 4683    cx.set_state(indoc! {"
 4684        aaa_bbbˇ
 4685    "});
 4686    cx.update_editor(|e, window, cx| {
 4687        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4688    });
 4689    cx.assert_editor_state(indoc! {"
 4690        «aaaBbbˇ»
 4691    "});
 4692
 4693    // Test to make sure we all aware of the fact that each word can grow and shrink
 4694    // Final selections should be aware of this fact
 4695    cx.set_state(indoc! {"
 4696        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4697    "});
 4698    cx.update_editor(|e, window, cx| {
 4699        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4700    });
 4701    cx.assert_editor_state(indoc! {"
 4702        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4703    "});
 4704
 4705    cx.set_state(indoc! {"
 4706        «hElLo, WoRld!ˇ»
 4707    "});
 4708    cx.update_editor(|e, window, cx| {
 4709        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4710    });
 4711    cx.assert_editor_state(indoc! {"
 4712        «HeLlO, wOrLD!ˇ»
 4713    "});
 4714}
 4715
 4716#[gpui::test]
 4717fn test_duplicate_line(cx: &mut TestAppContext) {
 4718    init_test(cx, |_| {});
 4719
 4720    let editor = cx.add_window(|window, cx| {
 4721        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4722        build_editor(buffer, window, cx)
 4723    });
 4724    _ = editor.update(cx, |editor, window, cx| {
 4725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4726            s.select_display_ranges([
 4727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4728                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4729                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4730                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4731            ])
 4732        });
 4733        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4734        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4735        assert_eq!(
 4736            editor.selections.display_ranges(cx),
 4737            vec![
 4738                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4740                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4741                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4742            ]
 4743        );
 4744    });
 4745
 4746    let editor = cx.add_window(|window, cx| {
 4747        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4748        build_editor(buffer, window, cx)
 4749    });
 4750    _ = editor.update(cx, |editor, window, cx| {
 4751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4752            s.select_display_ranges([
 4753                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4754                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4755            ])
 4756        });
 4757        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4758        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4759        assert_eq!(
 4760            editor.selections.display_ranges(cx),
 4761            vec![
 4762                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4763                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4764            ]
 4765        );
 4766    });
 4767
 4768    // With `move_upwards` the selections stay in place, except for
 4769    // the lines inserted above them
 4770    let editor = cx.add_window(|window, cx| {
 4771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4772        build_editor(buffer, window, cx)
 4773    });
 4774    _ = editor.update(cx, |editor, window, cx| {
 4775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4776            s.select_display_ranges([
 4777                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4778                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4779                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4780                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4781            ])
 4782        });
 4783        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4784        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4785        assert_eq!(
 4786            editor.selections.display_ranges(cx),
 4787            vec![
 4788                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4789                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4790                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4791                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4792            ]
 4793        );
 4794    });
 4795
 4796    let editor = cx.add_window(|window, cx| {
 4797        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4798        build_editor(buffer, window, cx)
 4799    });
 4800    _ = editor.update(cx, |editor, window, cx| {
 4801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4802            s.select_display_ranges([
 4803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4804                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4805            ])
 4806        });
 4807        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4808        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4809        assert_eq!(
 4810            editor.selections.display_ranges(cx),
 4811            vec![
 4812                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4813                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4814            ]
 4815        );
 4816    });
 4817
 4818    let editor = cx.add_window(|window, cx| {
 4819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4820        build_editor(buffer, window, cx)
 4821    });
 4822    _ = editor.update(cx, |editor, window, cx| {
 4823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4824            s.select_display_ranges([
 4825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4827            ])
 4828        });
 4829        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4830        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4831        assert_eq!(
 4832            editor.selections.display_ranges(cx),
 4833            vec![
 4834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4836            ]
 4837        );
 4838    });
 4839}
 4840
 4841#[gpui::test]
 4842fn test_move_line_up_down(cx: &mut TestAppContext) {
 4843    init_test(cx, |_| {});
 4844
 4845    let editor = cx.add_window(|window, cx| {
 4846        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4847        build_editor(buffer, window, cx)
 4848    });
 4849    _ = editor.update(cx, |editor, window, cx| {
 4850        editor.fold_creases(
 4851            vec![
 4852                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4853                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4854                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4855            ],
 4856            true,
 4857            window,
 4858            cx,
 4859        );
 4860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4861            s.select_display_ranges([
 4862                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4863                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4864                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4865                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4866            ])
 4867        });
 4868        assert_eq!(
 4869            editor.display_text(cx),
 4870            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4871        );
 4872
 4873        editor.move_line_up(&MoveLineUp, window, cx);
 4874        assert_eq!(
 4875            editor.display_text(cx),
 4876            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4877        );
 4878        assert_eq!(
 4879            editor.selections.display_ranges(cx),
 4880            vec![
 4881                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4882                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4883                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4884                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4885            ]
 4886        );
 4887    });
 4888
 4889    _ = editor.update(cx, |editor, window, cx| {
 4890        editor.move_line_down(&MoveLineDown, window, cx);
 4891        assert_eq!(
 4892            editor.display_text(cx),
 4893            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4894        );
 4895        assert_eq!(
 4896            editor.selections.display_ranges(cx),
 4897            vec![
 4898                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4899                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4900                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4901                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4902            ]
 4903        );
 4904    });
 4905
 4906    _ = editor.update(cx, |editor, window, cx| {
 4907        editor.move_line_down(&MoveLineDown, window, cx);
 4908        assert_eq!(
 4909            editor.display_text(cx),
 4910            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4911        );
 4912        assert_eq!(
 4913            editor.selections.display_ranges(cx),
 4914            vec![
 4915                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4916                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4917                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4918                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4919            ]
 4920        );
 4921    });
 4922
 4923    _ = editor.update(cx, |editor, window, cx| {
 4924        editor.move_line_up(&MoveLineUp, window, cx);
 4925        assert_eq!(
 4926            editor.display_text(cx),
 4927            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4928        );
 4929        assert_eq!(
 4930            editor.selections.display_ranges(cx),
 4931            vec![
 4932                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4933                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4934                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4936            ]
 4937        );
 4938    });
 4939}
 4940
 4941#[gpui::test]
 4942fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4943    init_test(cx, |_| {});
 4944
 4945    let editor = cx.add_window(|window, cx| {
 4946        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4947        build_editor(buffer, window, cx)
 4948    });
 4949    _ = editor.update(cx, |editor, window, cx| {
 4950        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4951        editor.insert_blocks(
 4952            [BlockProperties {
 4953                style: BlockStyle::Fixed,
 4954                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4955                height: Some(1),
 4956                render: Arc::new(|_| div().into_any()),
 4957                priority: 0,
 4958                render_in_minimap: true,
 4959            }],
 4960            Some(Autoscroll::fit()),
 4961            cx,
 4962        );
 4963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4964            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4965        });
 4966        editor.move_line_down(&MoveLineDown, window, cx);
 4967    });
 4968}
 4969
 4970#[gpui::test]
 4971async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4972    init_test(cx, |_| {});
 4973
 4974    let mut cx = EditorTestContext::new(cx).await;
 4975    cx.set_state(
 4976        &"
 4977            ˇzero
 4978            one
 4979            two
 4980            three
 4981            four
 4982            five
 4983        "
 4984        .unindent(),
 4985    );
 4986
 4987    // Create a four-line block that replaces three lines of text.
 4988    cx.update_editor(|editor, window, cx| {
 4989        let snapshot = editor.snapshot(window, cx);
 4990        let snapshot = &snapshot.buffer_snapshot;
 4991        let placement = BlockPlacement::Replace(
 4992            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4993        );
 4994        editor.insert_blocks(
 4995            [BlockProperties {
 4996                placement,
 4997                height: Some(4),
 4998                style: BlockStyle::Sticky,
 4999                render: Arc::new(|_| gpui::div().into_any_element()),
 5000                priority: 0,
 5001                render_in_minimap: true,
 5002            }],
 5003            None,
 5004            cx,
 5005        );
 5006    });
 5007
 5008    // Move down so that the cursor touches the block.
 5009    cx.update_editor(|editor, window, cx| {
 5010        editor.move_down(&Default::default(), window, cx);
 5011    });
 5012    cx.assert_editor_state(
 5013        &"
 5014            zero
 5015            «one
 5016            two
 5017            threeˇ»
 5018            four
 5019            five
 5020        "
 5021        .unindent(),
 5022    );
 5023
 5024    // Move down past the block.
 5025    cx.update_editor(|editor, window, cx| {
 5026        editor.move_down(&Default::default(), window, cx);
 5027    });
 5028    cx.assert_editor_state(
 5029        &"
 5030            zero
 5031            one
 5032            two
 5033            three
 5034            ˇfour
 5035            five
 5036        "
 5037        .unindent(),
 5038    );
 5039}
 5040
 5041#[gpui::test]
 5042fn test_transpose(cx: &mut TestAppContext) {
 5043    init_test(cx, |_| {});
 5044
 5045    _ = cx.add_window(|window, cx| {
 5046        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5047        editor.set_style(EditorStyle::default(), window, cx);
 5048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5049            s.select_ranges([1..1])
 5050        });
 5051        editor.transpose(&Default::default(), window, cx);
 5052        assert_eq!(editor.text(cx), "bac");
 5053        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5054
 5055        editor.transpose(&Default::default(), window, cx);
 5056        assert_eq!(editor.text(cx), "bca");
 5057        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5058
 5059        editor.transpose(&Default::default(), window, cx);
 5060        assert_eq!(editor.text(cx), "bac");
 5061        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5062
 5063        editor
 5064    });
 5065
 5066    _ = cx.add_window(|window, cx| {
 5067        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5068        editor.set_style(EditorStyle::default(), window, cx);
 5069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5070            s.select_ranges([3..3])
 5071        });
 5072        editor.transpose(&Default::default(), window, cx);
 5073        assert_eq!(editor.text(cx), "acb\nde");
 5074        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5075
 5076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5077            s.select_ranges([4..4])
 5078        });
 5079        editor.transpose(&Default::default(), window, cx);
 5080        assert_eq!(editor.text(cx), "acbd\ne");
 5081        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5082
 5083        editor.transpose(&Default::default(), window, cx);
 5084        assert_eq!(editor.text(cx), "acbde\n");
 5085        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5086
 5087        editor.transpose(&Default::default(), window, cx);
 5088        assert_eq!(editor.text(cx), "acbd\ne");
 5089        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5090
 5091        editor
 5092    });
 5093
 5094    _ = cx.add_window(|window, cx| {
 5095        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5096        editor.set_style(EditorStyle::default(), window, cx);
 5097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5098            s.select_ranges([1..1, 2..2, 4..4])
 5099        });
 5100        editor.transpose(&Default::default(), window, cx);
 5101        assert_eq!(editor.text(cx), "bacd\ne");
 5102        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5103
 5104        editor.transpose(&Default::default(), window, cx);
 5105        assert_eq!(editor.text(cx), "bcade\n");
 5106        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5107
 5108        editor.transpose(&Default::default(), window, cx);
 5109        assert_eq!(editor.text(cx), "bcda\ne");
 5110        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5111
 5112        editor.transpose(&Default::default(), window, cx);
 5113        assert_eq!(editor.text(cx), "bcade\n");
 5114        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5115
 5116        editor.transpose(&Default::default(), window, cx);
 5117        assert_eq!(editor.text(cx), "bcaed\n");
 5118        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5119
 5120        editor
 5121    });
 5122
 5123    _ = cx.add_window(|window, cx| {
 5124        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5125        editor.set_style(EditorStyle::default(), window, cx);
 5126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5127            s.select_ranges([4..4])
 5128        });
 5129        editor.transpose(&Default::default(), window, cx);
 5130        assert_eq!(editor.text(cx), "🏀🍐✋");
 5131        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5132
 5133        editor.transpose(&Default::default(), window, cx);
 5134        assert_eq!(editor.text(cx), "🏀✋🍐");
 5135        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5136
 5137        editor.transpose(&Default::default(), window, cx);
 5138        assert_eq!(editor.text(cx), "🏀🍐✋");
 5139        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5140
 5141        editor
 5142    });
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_rewrap(cx: &mut TestAppContext) {
 5147    init_test(cx, |settings| {
 5148        settings.languages.0.extend([
 5149            (
 5150                "Markdown".into(),
 5151                LanguageSettingsContent {
 5152                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5153                    preferred_line_length: Some(40),
 5154                    ..Default::default()
 5155                },
 5156            ),
 5157            (
 5158                "Plain Text".into(),
 5159                LanguageSettingsContent {
 5160                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5161                    preferred_line_length: Some(40),
 5162                    ..Default::default()
 5163                },
 5164            ),
 5165            (
 5166                "C++".into(),
 5167                LanguageSettingsContent {
 5168                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5169                    preferred_line_length: Some(40),
 5170                    ..Default::default()
 5171                },
 5172            ),
 5173            (
 5174                "Python".into(),
 5175                LanguageSettingsContent {
 5176                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5177                    preferred_line_length: Some(40),
 5178                    ..Default::default()
 5179                },
 5180            ),
 5181            (
 5182                "Rust".into(),
 5183                LanguageSettingsContent {
 5184                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5185                    preferred_line_length: Some(40),
 5186                    ..Default::default()
 5187                },
 5188            ),
 5189        ])
 5190    });
 5191
 5192    let mut cx = EditorTestContext::new(cx).await;
 5193
 5194    let cpp_language = Arc::new(Language::new(
 5195        LanguageConfig {
 5196            name: "C++".into(),
 5197            line_comments: vec!["// ".into()],
 5198            ..LanguageConfig::default()
 5199        },
 5200        None,
 5201    ));
 5202    let python_language = Arc::new(Language::new(
 5203        LanguageConfig {
 5204            name: "Python".into(),
 5205            line_comments: vec!["# ".into()],
 5206            ..LanguageConfig::default()
 5207        },
 5208        None,
 5209    ));
 5210    let markdown_language = Arc::new(Language::new(
 5211        LanguageConfig {
 5212            name: "Markdown".into(),
 5213            rewrap_prefixes: vec![
 5214                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5215                regex::Regex::new("[-*+]\\s+").unwrap(),
 5216            ],
 5217            ..LanguageConfig::default()
 5218        },
 5219        None,
 5220    ));
 5221    let rust_language = Arc::new(Language::new(
 5222        LanguageConfig {
 5223            name: "Rust".into(),
 5224            line_comments: vec!["// ".into(), "/// ".into()],
 5225            ..LanguageConfig::default()
 5226        },
 5227        Some(tree_sitter_rust::LANGUAGE.into()),
 5228    ));
 5229
 5230    let plaintext_language = Arc::new(Language::new(
 5231        LanguageConfig {
 5232            name: "Plain Text".into(),
 5233            ..LanguageConfig::default()
 5234        },
 5235        None,
 5236    ));
 5237
 5238    // Test basic rewrapping of a long line with a cursor
 5239    assert_rewrap(
 5240        indoc! {"
 5241            // ˇThis is a long comment that needs to be wrapped.
 5242        "},
 5243        indoc! {"
 5244            // ˇThis is a long comment that needs to
 5245            // be wrapped.
 5246        "},
 5247        cpp_language.clone(),
 5248        &mut cx,
 5249    );
 5250
 5251    // Test rewrapping a full selection
 5252    assert_rewrap(
 5253        indoc! {"
 5254            «// This selected long comment needs to be wrapped.ˇ»"
 5255        },
 5256        indoc! {"
 5257            «// This selected long comment needs to
 5258            // be wrapped.ˇ»"
 5259        },
 5260        cpp_language.clone(),
 5261        &mut cx,
 5262    );
 5263
 5264    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5265    assert_rewrap(
 5266        indoc! {"
 5267            // ˇThis is the first line.
 5268            // Thisˇ is the second line.
 5269            // This is the thirdˇ line, all part of one paragraph.
 5270         "},
 5271        indoc! {"
 5272            // ˇThis is the first line. Thisˇ is the
 5273            // second line. This is the thirdˇ line,
 5274            // all part of one paragraph.
 5275         "},
 5276        cpp_language.clone(),
 5277        &mut cx,
 5278    );
 5279
 5280    // Test multiple cursors in different paragraphs trigger separate rewraps
 5281    assert_rewrap(
 5282        indoc! {"
 5283            // ˇThis is the first paragraph, first line.
 5284            // ˇThis is the first paragraph, second line.
 5285
 5286            // ˇThis is the second paragraph, first line.
 5287            // ˇThis is the second paragraph, second line.
 5288        "},
 5289        indoc! {"
 5290            // ˇThis is the first paragraph, first
 5291            // line. ˇThis is the first paragraph,
 5292            // second line.
 5293
 5294            // ˇThis is the second paragraph, first
 5295            // line. ˇThis is the second paragraph,
 5296            // second line.
 5297        "},
 5298        cpp_language.clone(),
 5299        &mut cx,
 5300    );
 5301
 5302    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5303    assert_rewrap(
 5304        indoc! {"
 5305            «// A regular long long comment to be wrapped.
 5306            /// A documentation long comment to be wrapped.ˇ»
 5307          "},
 5308        indoc! {"
 5309            «// A regular long long comment to be
 5310            // wrapped.
 5311            /// A documentation long comment to be
 5312            /// wrapped.ˇ»
 5313          "},
 5314        rust_language.clone(),
 5315        &mut cx,
 5316    );
 5317
 5318    // Test that change in indentation level trigger seperate rewraps
 5319    assert_rewrap(
 5320        indoc! {"
 5321            fn foo() {
 5322                «// This is a long comment at the base indent.
 5323                    // This is a long comment at the next indent.ˇ»
 5324            }
 5325        "},
 5326        indoc! {"
 5327            fn foo() {
 5328                «// This is a long comment at the
 5329                // base indent.
 5330                    // This is a long comment at the
 5331                    // next indent.ˇ»
 5332            }
 5333        "},
 5334        rust_language.clone(),
 5335        &mut cx,
 5336    );
 5337
 5338    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5339    assert_rewrap(
 5340        indoc! {"
 5341            # ˇThis is a long comment using a pound sign.
 5342        "},
 5343        indoc! {"
 5344            # ˇThis is a long comment using a pound
 5345            # sign.
 5346        "},
 5347        python_language.clone(),
 5348        &mut cx,
 5349    );
 5350
 5351    // Test rewrapping only affects comments, not code even when selected
 5352    assert_rewrap(
 5353        indoc! {"
 5354            «/// This doc comment is long and should be wrapped.
 5355            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5356        "},
 5357        indoc! {"
 5358            «/// This doc comment is long and should
 5359            /// be wrapped.
 5360            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5361        "},
 5362        rust_language.clone(),
 5363        &mut cx,
 5364    );
 5365
 5366    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5367    assert_rewrap(
 5368        indoc! {"
 5369            # Header
 5370
 5371            A long long long line of markdown text to wrap.ˇ
 5372         "},
 5373        indoc! {"
 5374            # Header
 5375
 5376            A long long long line of markdown text
 5377            to wrap.ˇ
 5378         "},
 5379        markdown_language.clone(),
 5380        &mut cx,
 5381    );
 5382
 5383    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5384    assert_rewrap(
 5385        indoc! {"
 5386            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5387            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5388            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5389        "},
 5390        indoc! {"
 5391            «1. This is a numbered list item that is
 5392               very long and needs to be wrapped
 5393               properly.
 5394            2. This is a numbered list item that is
 5395               very long and needs to be wrapped
 5396               properly.
 5397            - This is an unordered list item that is
 5398              also very long and should not merge
 5399              with the numbered item.ˇ»
 5400        "},
 5401        markdown_language.clone(),
 5402        &mut cx,
 5403    );
 5404
 5405    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5406    assert_rewrap(
 5407        indoc! {"
 5408            «1. This is a numbered list item that is
 5409            very long and needs to be wrapped
 5410            properly.
 5411            2. This is a numbered list item that is
 5412            very long and needs to be wrapped
 5413            properly.
 5414            - This is an unordered list item that is
 5415            also very long and should not merge with
 5416            the numbered item.ˇ»
 5417        "},
 5418        indoc! {"
 5419            «1. This is a numbered list item that is
 5420               very long and needs to be wrapped
 5421               properly.
 5422            2. This is a numbered list item that is
 5423               very long and needs to be wrapped
 5424               properly.
 5425            - This is an unordered list item that is
 5426              also very long and should not merge
 5427              with the numbered item.ˇ»
 5428        "},
 5429        markdown_language.clone(),
 5430        &mut cx,
 5431    );
 5432
 5433    // Test that rewrapping maintain indents even when they already exists.
 5434    assert_rewrap(
 5435        indoc! {"
 5436            «1. This is a numbered list
 5437               item that is very long and needs to be wrapped properly.
 5438            2. This is a numbered list
 5439               item that is very long and needs to be wrapped properly.
 5440            - This is an unordered list item that is also very long and
 5441              should not merge with the numbered item.ˇ»
 5442        "},
 5443        indoc! {"
 5444            «1. This is a numbered list item that is
 5445               very long and needs to be wrapped
 5446               properly.
 5447            2. This is a numbered list item that is
 5448               very long and needs to be wrapped
 5449               properly.
 5450            - This is an unordered list item that is
 5451              also very long and should not merge
 5452              with the numbered item.ˇ»
 5453        "},
 5454        markdown_language.clone(),
 5455        &mut cx,
 5456    );
 5457
 5458    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5459    assert_rewrap(
 5460        indoc! {"
 5461            ˇThis is a very long line of plain text that will be wrapped.
 5462        "},
 5463        indoc! {"
 5464            ˇThis is a very long line of plain text
 5465            that will be wrapped.
 5466        "},
 5467        plaintext_language.clone(),
 5468        &mut cx,
 5469    );
 5470
 5471    // Test that non-commented code acts as a paragraph boundary within a selection
 5472    assert_rewrap(
 5473        indoc! {"
 5474               «// This is the first long comment block to be wrapped.
 5475               fn my_func(a: u32);
 5476               // This is the second long comment block to be wrapped.ˇ»
 5477           "},
 5478        indoc! {"
 5479               «// This is the first long comment block
 5480               // to be wrapped.
 5481               fn my_func(a: u32);
 5482               // This is the second long comment block
 5483               // to be wrapped.ˇ»
 5484           "},
 5485        rust_language.clone(),
 5486        &mut cx,
 5487    );
 5488
 5489    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5490    assert_rewrap(
 5491        indoc! {"
 5492            «ˇThis is a very long line that will be wrapped.
 5493
 5494            This is another paragraph in the same selection.»
 5495
 5496            «\tThis is a very long indented line that will be wrapped.ˇ»
 5497         "},
 5498        indoc! {"
 5499            «ˇThis is a very long line that will be
 5500            wrapped.
 5501
 5502            This is another paragraph in the same
 5503            selection.»
 5504
 5505            «\tThis is a very long indented line
 5506            \tthat will be wrapped.ˇ»
 5507         "},
 5508        plaintext_language.clone(),
 5509        &mut cx,
 5510    );
 5511
 5512    // Test that an empty comment line acts as a paragraph boundary
 5513    assert_rewrap(
 5514        indoc! {"
 5515            // ˇThis is a long comment that will be wrapped.
 5516            //
 5517            // And this is another long comment that will also be wrapped.ˇ
 5518         "},
 5519        indoc! {"
 5520            // ˇThis is a long comment that will be
 5521            // wrapped.
 5522            //
 5523            // And this is another long comment that
 5524            // will also be wrapped.ˇ
 5525         "},
 5526        cpp_language,
 5527        &mut cx,
 5528    );
 5529
 5530    #[track_caller]
 5531    fn assert_rewrap(
 5532        unwrapped_text: &str,
 5533        wrapped_text: &str,
 5534        language: Arc<Language>,
 5535        cx: &mut EditorTestContext,
 5536    ) {
 5537        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5538        cx.set_state(unwrapped_text);
 5539        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5540        cx.assert_editor_state(wrapped_text);
 5541    }
 5542}
 5543
 5544#[gpui::test]
 5545async fn test_hard_wrap(cx: &mut TestAppContext) {
 5546    init_test(cx, |_| {});
 5547    let mut cx = EditorTestContext::new(cx).await;
 5548
 5549    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5550    cx.update_editor(|editor, _, cx| {
 5551        editor.set_hard_wrap(Some(14), cx);
 5552    });
 5553
 5554    cx.set_state(indoc!(
 5555        "
 5556        one two three ˇ
 5557        "
 5558    ));
 5559    cx.simulate_input("four");
 5560    cx.run_until_parked();
 5561
 5562    cx.assert_editor_state(indoc!(
 5563        "
 5564        one two three
 5565        fourˇ
 5566        "
 5567    ));
 5568
 5569    cx.update_editor(|editor, window, cx| {
 5570        editor.newline(&Default::default(), window, cx);
 5571    });
 5572    cx.run_until_parked();
 5573    cx.assert_editor_state(indoc!(
 5574        "
 5575        one two three
 5576        four
 5577        ˇ
 5578        "
 5579    ));
 5580
 5581    cx.simulate_input("five");
 5582    cx.run_until_parked();
 5583    cx.assert_editor_state(indoc!(
 5584        "
 5585        one two three
 5586        four
 5587        fiveˇ
 5588        "
 5589    ));
 5590
 5591    cx.update_editor(|editor, window, cx| {
 5592        editor.newline(&Default::default(), window, cx);
 5593    });
 5594    cx.run_until_parked();
 5595    cx.simulate_input("# ");
 5596    cx.run_until_parked();
 5597    cx.assert_editor_state(indoc!(
 5598        "
 5599        one two three
 5600        four
 5601        five
 5602        # ˇ
 5603        "
 5604    ));
 5605
 5606    cx.update_editor(|editor, window, cx| {
 5607        editor.newline(&Default::default(), window, cx);
 5608    });
 5609    cx.run_until_parked();
 5610    cx.assert_editor_state(indoc!(
 5611        "
 5612        one two three
 5613        four
 5614        five
 5615        #\x20
 5616 5617        "
 5618    ));
 5619
 5620    cx.simulate_input(" 6");
 5621    cx.run_until_parked();
 5622    cx.assert_editor_state(indoc!(
 5623        "
 5624        one two three
 5625        four
 5626        five
 5627        #
 5628        # 6ˇ
 5629        "
 5630    ));
 5631}
 5632
 5633#[gpui::test]
 5634async fn test_clipboard(cx: &mut TestAppContext) {
 5635    init_test(cx, |_| {});
 5636
 5637    let mut cx = EditorTestContext::new(cx).await;
 5638
 5639    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5640    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5641    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5642
 5643    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5644    cx.set_state("two ˇfour ˇsix ˇ");
 5645    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5646    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5647
 5648    // Paste again but with only two cursors. Since the number of cursors doesn't
 5649    // match the number of slices in the clipboard, the entire clipboard text
 5650    // is pasted at each cursor.
 5651    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5652    cx.update_editor(|e, window, cx| {
 5653        e.handle_input("( ", window, cx);
 5654        e.paste(&Paste, window, cx);
 5655        e.handle_input(") ", window, cx);
 5656    });
 5657    cx.assert_editor_state(
 5658        &([
 5659            "( one✅ ",
 5660            "three ",
 5661            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5662            "three ",
 5663            "five ) ˇ",
 5664        ]
 5665        .join("\n")),
 5666    );
 5667
 5668    // Cut with three selections, one of which is full-line.
 5669    cx.set_state(indoc! {"
 5670        1«2ˇ»3
 5671        4ˇ567
 5672        «8ˇ»9"});
 5673    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5674    cx.assert_editor_state(indoc! {"
 5675        1ˇ3
 5676        ˇ9"});
 5677
 5678    // Paste with three selections, noticing how the copied selection that was full-line
 5679    // gets inserted before the second cursor.
 5680    cx.set_state(indoc! {"
 5681        1ˇ3
 5682 5683        «oˇ»ne"});
 5684    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5685    cx.assert_editor_state(indoc! {"
 5686        12ˇ3
 5687        4567
 5688 5689        8ˇne"});
 5690
 5691    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5692    cx.set_state(indoc! {"
 5693        The quick brown
 5694        fox juˇmps over
 5695        the lazy dog"});
 5696    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5697    assert_eq!(
 5698        cx.read_from_clipboard()
 5699            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5700        Some("fox jumps over\n".to_string())
 5701    );
 5702
 5703    // Paste with three selections, noticing how the copied full-line selection is inserted
 5704    // before the empty selections but replaces the selection that is non-empty.
 5705    cx.set_state(indoc! {"
 5706        Tˇhe quick brown
 5707        «foˇ»x jumps over
 5708        tˇhe lazy dog"});
 5709    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5710    cx.assert_editor_state(indoc! {"
 5711        fox jumps over
 5712        Tˇhe quick brown
 5713        fox jumps over
 5714        ˇx jumps over
 5715        fox jumps over
 5716        tˇhe lazy dog"});
 5717}
 5718
 5719#[gpui::test]
 5720async fn test_copy_trim(cx: &mut TestAppContext) {
 5721    init_test(cx, |_| {});
 5722
 5723    let mut cx = EditorTestContext::new(cx).await;
 5724    cx.set_state(
 5725        r#"            «for selection in selections.iter() {
 5726            let mut start = selection.start;
 5727            let mut end = selection.end;
 5728            let is_entire_line = selection.is_empty();
 5729            if is_entire_line {
 5730                start = Point::new(start.row, 0);ˇ»
 5731                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5732            }
 5733        "#,
 5734    );
 5735    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5736    assert_eq!(
 5737        cx.read_from_clipboard()
 5738            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5739        Some(
 5740            "for selection in selections.iter() {
 5741            let mut start = selection.start;
 5742            let mut end = selection.end;
 5743            let is_entire_line = selection.is_empty();
 5744            if is_entire_line {
 5745                start = Point::new(start.row, 0);"
 5746                .to_string()
 5747        ),
 5748        "Regular copying preserves all indentation selected",
 5749    );
 5750    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5751    assert_eq!(
 5752        cx.read_from_clipboard()
 5753            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5754        Some(
 5755            "for selection in selections.iter() {
 5756let mut start = selection.start;
 5757let mut end = selection.end;
 5758let is_entire_line = selection.is_empty();
 5759if is_entire_line {
 5760    start = Point::new(start.row, 0);"
 5761                .to_string()
 5762        ),
 5763        "Copying with stripping should strip all leading whitespaces"
 5764    );
 5765
 5766    cx.set_state(
 5767        r#"       «     for selection in selections.iter() {
 5768            let mut start = selection.start;
 5769            let mut end = selection.end;
 5770            let is_entire_line = selection.is_empty();
 5771            if is_entire_line {
 5772                start = Point::new(start.row, 0);ˇ»
 5773                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5774            }
 5775        "#,
 5776    );
 5777    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5778    assert_eq!(
 5779        cx.read_from_clipboard()
 5780            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5781        Some(
 5782            "     for selection in selections.iter() {
 5783            let mut start = selection.start;
 5784            let mut end = selection.end;
 5785            let is_entire_line = selection.is_empty();
 5786            if is_entire_line {
 5787                start = Point::new(start.row, 0);"
 5788                .to_string()
 5789        ),
 5790        "Regular copying preserves all indentation selected",
 5791    );
 5792    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5793    assert_eq!(
 5794        cx.read_from_clipboard()
 5795            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5796        Some(
 5797            "for selection in selections.iter() {
 5798let mut start = selection.start;
 5799let mut end = selection.end;
 5800let is_entire_line = selection.is_empty();
 5801if is_entire_line {
 5802    start = Point::new(start.row, 0);"
 5803                .to_string()
 5804        ),
 5805        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5806    );
 5807
 5808    cx.set_state(
 5809        r#"       «ˇ     for selection in selections.iter() {
 5810            let mut start = selection.start;
 5811            let mut end = selection.end;
 5812            let is_entire_line = selection.is_empty();
 5813            if is_entire_line {
 5814                start = Point::new(start.row, 0);»
 5815                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5816            }
 5817        "#,
 5818    );
 5819    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5820    assert_eq!(
 5821        cx.read_from_clipboard()
 5822            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5823        Some(
 5824            "     for selection in selections.iter() {
 5825            let mut start = selection.start;
 5826            let mut end = selection.end;
 5827            let is_entire_line = selection.is_empty();
 5828            if is_entire_line {
 5829                start = Point::new(start.row, 0);"
 5830                .to_string()
 5831        ),
 5832        "Regular copying for reverse selection works the same",
 5833    );
 5834    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5835    assert_eq!(
 5836        cx.read_from_clipboard()
 5837            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5838        Some(
 5839            "for selection in selections.iter() {
 5840let mut start = selection.start;
 5841let mut end = selection.end;
 5842let is_entire_line = selection.is_empty();
 5843if is_entire_line {
 5844    start = Point::new(start.row, 0);"
 5845                .to_string()
 5846        ),
 5847        "Copying with stripping for reverse selection works the same"
 5848    );
 5849
 5850    cx.set_state(
 5851        r#"            for selection «in selections.iter() {
 5852            let mut start = selection.start;
 5853            let mut end = selection.end;
 5854            let is_entire_line = selection.is_empty();
 5855            if is_entire_line {
 5856                start = Point::new(start.row, 0);ˇ»
 5857                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5858            }
 5859        "#,
 5860    );
 5861    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5862    assert_eq!(
 5863        cx.read_from_clipboard()
 5864            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5865        Some(
 5866            "in selections.iter() {
 5867            let mut start = selection.start;
 5868            let mut end = selection.end;
 5869            let is_entire_line = selection.is_empty();
 5870            if is_entire_line {
 5871                start = Point::new(start.row, 0);"
 5872                .to_string()
 5873        ),
 5874        "When selecting past the indent, the copying works as usual",
 5875    );
 5876    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5877    assert_eq!(
 5878        cx.read_from_clipboard()
 5879            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5880        Some(
 5881            "in selections.iter() {
 5882            let mut start = selection.start;
 5883            let mut end = selection.end;
 5884            let is_entire_line = selection.is_empty();
 5885            if is_entire_line {
 5886                start = Point::new(start.row, 0);"
 5887                .to_string()
 5888        ),
 5889        "When selecting past the indent, nothing is trimmed"
 5890    );
 5891
 5892    cx.set_state(
 5893        r#"            «for selection in selections.iter() {
 5894            let mut start = selection.start;
 5895
 5896            let mut end = selection.end;
 5897            let is_entire_line = selection.is_empty();
 5898            if is_entire_line {
 5899                start = Point::new(start.row, 0);
 5900ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5901            }
 5902        "#,
 5903    );
 5904    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5905    assert_eq!(
 5906        cx.read_from_clipboard()
 5907            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5908        Some(
 5909            "for selection in selections.iter() {
 5910let mut start = selection.start;
 5911
 5912let mut end = selection.end;
 5913let is_entire_line = selection.is_empty();
 5914if is_entire_line {
 5915    start = Point::new(start.row, 0);
 5916"
 5917            .to_string()
 5918        ),
 5919        "Copying with stripping should ignore empty lines"
 5920    );
 5921}
 5922
 5923#[gpui::test]
 5924async fn test_paste_multiline(cx: &mut TestAppContext) {
 5925    init_test(cx, |_| {});
 5926
 5927    let mut cx = EditorTestContext::new(cx).await;
 5928    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5929
 5930    // Cut an indented block, without the leading whitespace.
 5931    cx.set_state(indoc! {"
 5932        const a: B = (
 5933            c(),
 5934            «d(
 5935                e,
 5936                f
 5937            )ˇ»
 5938        );
 5939    "});
 5940    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5941    cx.assert_editor_state(indoc! {"
 5942        const a: B = (
 5943            c(),
 5944            ˇ
 5945        );
 5946    "});
 5947
 5948    // Paste it at the same position.
 5949    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5950    cx.assert_editor_state(indoc! {"
 5951        const a: B = (
 5952            c(),
 5953            d(
 5954                e,
 5955                f
 5956 5957        );
 5958    "});
 5959
 5960    // Paste it at a line with a lower indent level.
 5961    cx.set_state(indoc! {"
 5962        ˇ
 5963        const a: B = (
 5964            c(),
 5965        );
 5966    "});
 5967    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5968    cx.assert_editor_state(indoc! {"
 5969        d(
 5970            e,
 5971            f
 5972 5973        const a: B = (
 5974            c(),
 5975        );
 5976    "});
 5977
 5978    // Cut an indented block, with the leading whitespace.
 5979    cx.set_state(indoc! {"
 5980        const a: B = (
 5981            c(),
 5982        «    d(
 5983                e,
 5984                f
 5985            )
 5986        ˇ»);
 5987    "});
 5988    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5989    cx.assert_editor_state(indoc! {"
 5990        const a: B = (
 5991            c(),
 5992        ˇ);
 5993    "});
 5994
 5995    // Paste it at the same position.
 5996    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5997    cx.assert_editor_state(indoc! {"
 5998        const a: B = (
 5999            c(),
 6000            d(
 6001                e,
 6002                f
 6003            )
 6004        ˇ);
 6005    "});
 6006
 6007    // Paste it at a line with a higher indent level.
 6008    cx.set_state(indoc! {"
 6009        const a: B = (
 6010            c(),
 6011            d(
 6012                e,
 6013 6014            )
 6015        );
 6016    "});
 6017    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6018    cx.assert_editor_state(indoc! {"
 6019        const a: B = (
 6020            c(),
 6021            d(
 6022                e,
 6023                f    d(
 6024                    e,
 6025                    f
 6026                )
 6027        ˇ
 6028            )
 6029        );
 6030    "});
 6031
 6032    // Copy an indented block, starting mid-line
 6033    cx.set_state(indoc! {"
 6034        const a: B = (
 6035            c(),
 6036            somethin«g(
 6037                e,
 6038                f
 6039            )ˇ»
 6040        );
 6041    "});
 6042    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6043
 6044    // Paste it on a line with a lower indent level
 6045    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6046    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6047    cx.assert_editor_state(indoc! {"
 6048        const a: B = (
 6049            c(),
 6050            something(
 6051                e,
 6052                f
 6053            )
 6054        );
 6055        g(
 6056            e,
 6057            f
 6058"});
 6059}
 6060
 6061#[gpui::test]
 6062async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6063    init_test(cx, |_| {});
 6064
 6065    cx.write_to_clipboard(ClipboardItem::new_string(
 6066        "    d(\n        e\n    );\n".into(),
 6067    ));
 6068
 6069    let mut cx = EditorTestContext::new(cx).await;
 6070    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6071
 6072    cx.set_state(indoc! {"
 6073        fn a() {
 6074            b();
 6075            if c() {
 6076                ˇ
 6077            }
 6078        }
 6079    "});
 6080
 6081    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6082    cx.assert_editor_state(indoc! {"
 6083        fn a() {
 6084            b();
 6085            if c() {
 6086                d(
 6087                    e
 6088                );
 6089        ˇ
 6090            }
 6091        }
 6092    "});
 6093
 6094    cx.set_state(indoc! {"
 6095        fn a() {
 6096            b();
 6097            ˇ
 6098        }
 6099    "});
 6100
 6101    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6102    cx.assert_editor_state(indoc! {"
 6103        fn a() {
 6104            b();
 6105            d(
 6106                e
 6107            );
 6108        ˇ
 6109        }
 6110    "});
 6111}
 6112
 6113#[gpui::test]
 6114fn test_select_all(cx: &mut TestAppContext) {
 6115    init_test(cx, |_| {});
 6116
 6117    let editor = cx.add_window(|window, cx| {
 6118        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6119        build_editor(buffer, window, cx)
 6120    });
 6121    _ = editor.update(cx, |editor, window, cx| {
 6122        editor.select_all(&SelectAll, window, cx);
 6123        assert_eq!(
 6124            editor.selections.display_ranges(cx),
 6125            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6126        );
 6127    });
 6128}
 6129
 6130#[gpui::test]
 6131fn test_select_line(cx: &mut TestAppContext) {
 6132    init_test(cx, |_| {});
 6133
 6134    let editor = cx.add_window(|window, cx| {
 6135        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6136        build_editor(buffer, window, cx)
 6137    });
 6138    _ = editor.update(cx, |editor, window, cx| {
 6139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6140            s.select_display_ranges([
 6141                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6142                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6143                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6144                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6145            ])
 6146        });
 6147        editor.select_line(&SelectLine, window, cx);
 6148        assert_eq!(
 6149            editor.selections.display_ranges(cx),
 6150            vec![
 6151                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6152                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6153            ]
 6154        );
 6155    });
 6156
 6157    _ = editor.update(cx, |editor, window, cx| {
 6158        editor.select_line(&SelectLine, window, cx);
 6159        assert_eq!(
 6160            editor.selections.display_ranges(cx),
 6161            vec![
 6162                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6163                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6164            ]
 6165        );
 6166    });
 6167
 6168    _ = editor.update(cx, |editor, window, cx| {
 6169        editor.select_line(&SelectLine, window, cx);
 6170        assert_eq!(
 6171            editor.selections.display_ranges(cx),
 6172            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6173        );
 6174    });
 6175}
 6176
 6177#[gpui::test]
 6178async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6179    init_test(cx, |_| {});
 6180    let mut cx = EditorTestContext::new(cx).await;
 6181
 6182    #[track_caller]
 6183    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6184        cx.set_state(initial_state);
 6185        cx.update_editor(|e, window, cx| {
 6186            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6187        });
 6188        cx.assert_editor_state(expected_state);
 6189    }
 6190
 6191    // Selection starts and ends at the middle of lines, left-to-right
 6192    test(
 6193        &mut cx,
 6194        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6195        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6196    );
 6197    // Same thing, right-to-left
 6198    test(
 6199        &mut cx,
 6200        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6201        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6202    );
 6203
 6204    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6205    test(
 6206        &mut cx,
 6207        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6208        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6209    );
 6210    // Same thing, right-to-left
 6211    test(
 6212        &mut cx,
 6213        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6214        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6215    );
 6216
 6217    // Whole buffer, left-to-right, last line ends with newline
 6218    test(
 6219        &mut cx,
 6220        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6221        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6222    );
 6223    // Same thing, right-to-left
 6224    test(
 6225        &mut cx,
 6226        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6227        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6228    );
 6229
 6230    // Starts at the end of a line, ends at the start of another
 6231    test(
 6232        &mut cx,
 6233        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6234        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6235    );
 6236}
 6237
 6238#[gpui::test]
 6239async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6240    init_test(cx, |_| {});
 6241
 6242    let editor = cx.add_window(|window, cx| {
 6243        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6244        build_editor(buffer, window, cx)
 6245    });
 6246
 6247    // setup
 6248    _ = editor.update(cx, |editor, window, cx| {
 6249        editor.fold_creases(
 6250            vec![
 6251                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6252                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6253                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6254            ],
 6255            true,
 6256            window,
 6257            cx,
 6258        );
 6259        assert_eq!(
 6260            editor.display_text(cx),
 6261            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6262        );
 6263    });
 6264
 6265    _ = editor.update(cx, |editor, window, cx| {
 6266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6267            s.select_display_ranges([
 6268                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6269                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6270                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6271                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6272            ])
 6273        });
 6274        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6275        assert_eq!(
 6276            editor.display_text(cx),
 6277            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6278        );
 6279    });
 6280    EditorTestContext::for_editor(editor, cx)
 6281        .await
 6282        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6283
 6284    _ = editor.update(cx, |editor, window, cx| {
 6285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6286            s.select_display_ranges([
 6287                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6288            ])
 6289        });
 6290        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6291        assert_eq!(
 6292            editor.display_text(cx),
 6293            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6294        );
 6295        assert_eq!(
 6296            editor.selections.display_ranges(cx),
 6297            [
 6298                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6299                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6300                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6301                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6302                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6303                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6304                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6305            ]
 6306        );
 6307    });
 6308    EditorTestContext::for_editor(editor, cx)
 6309        .await
 6310        .assert_editor_state(
 6311            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6312        );
 6313}
 6314
 6315#[gpui::test]
 6316async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6317    init_test(cx, |_| {});
 6318
 6319    let mut cx = EditorTestContext::new(cx).await;
 6320
 6321    cx.set_state(indoc!(
 6322        r#"abc
 6323           defˇghi
 6324
 6325           jk
 6326           nlmo
 6327           "#
 6328    ));
 6329
 6330    cx.update_editor(|editor, window, cx| {
 6331        editor.add_selection_above(&Default::default(), window, cx);
 6332    });
 6333
 6334    cx.assert_editor_state(indoc!(
 6335        r#"abcˇ
 6336           defˇghi
 6337
 6338           jk
 6339           nlmo
 6340           "#
 6341    ));
 6342
 6343    cx.update_editor(|editor, window, cx| {
 6344        editor.add_selection_above(&Default::default(), window, cx);
 6345    });
 6346
 6347    cx.assert_editor_state(indoc!(
 6348        r#"abcˇ
 6349            defˇghi
 6350
 6351            jk
 6352            nlmo
 6353            "#
 6354    ));
 6355
 6356    cx.update_editor(|editor, window, cx| {
 6357        editor.add_selection_below(&Default::default(), window, cx);
 6358    });
 6359
 6360    cx.assert_editor_state(indoc!(
 6361        r#"abc
 6362           defˇghi
 6363
 6364           jk
 6365           nlmo
 6366           "#
 6367    ));
 6368
 6369    cx.update_editor(|editor, window, cx| {
 6370        editor.undo_selection(&Default::default(), window, cx);
 6371    });
 6372
 6373    cx.assert_editor_state(indoc!(
 6374        r#"abcˇ
 6375           defˇghi
 6376
 6377           jk
 6378           nlmo
 6379           "#
 6380    ));
 6381
 6382    cx.update_editor(|editor, window, cx| {
 6383        editor.redo_selection(&Default::default(), window, cx);
 6384    });
 6385
 6386    cx.assert_editor_state(indoc!(
 6387        r#"abc
 6388           defˇghi
 6389
 6390           jk
 6391           nlmo
 6392           "#
 6393    ));
 6394
 6395    cx.update_editor(|editor, window, cx| {
 6396        editor.add_selection_below(&Default::default(), window, cx);
 6397    });
 6398
 6399    cx.assert_editor_state(indoc!(
 6400        r#"abc
 6401           defˇghi
 6402           ˇ
 6403           jk
 6404           nlmo
 6405           "#
 6406    ));
 6407
 6408    cx.update_editor(|editor, window, cx| {
 6409        editor.add_selection_below(&Default::default(), window, cx);
 6410    });
 6411
 6412    cx.assert_editor_state(indoc!(
 6413        r#"abc
 6414           defˇghi
 6415           ˇ
 6416           jkˇ
 6417           nlmo
 6418           "#
 6419    ));
 6420
 6421    cx.update_editor(|editor, window, cx| {
 6422        editor.add_selection_below(&Default::default(), window, cx);
 6423    });
 6424
 6425    cx.assert_editor_state(indoc!(
 6426        r#"abc
 6427           defˇghi
 6428           ˇ
 6429           jkˇ
 6430           nlmˇo
 6431           "#
 6432    ));
 6433
 6434    cx.update_editor(|editor, window, cx| {
 6435        editor.add_selection_below(&Default::default(), window, cx);
 6436    });
 6437
 6438    cx.assert_editor_state(indoc!(
 6439        r#"abc
 6440           defˇghi
 6441           ˇ
 6442           jkˇ
 6443           nlmˇo
 6444           ˇ"#
 6445    ));
 6446
 6447    // change selections
 6448    cx.set_state(indoc!(
 6449        r#"abc
 6450           def«ˇg»hi
 6451
 6452           jk
 6453           nlmo
 6454           "#
 6455    ));
 6456
 6457    cx.update_editor(|editor, window, cx| {
 6458        editor.add_selection_below(&Default::default(), window, cx);
 6459    });
 6460
 6461    cx.assert_editor_state(indoc!(
 6462        r#"abc
 6463           def«ˇg»hi
 6464
 6465           jk
 6466           nlm«ˇo»
 6467           "#
 6468    ));
 6469
 6470    cx.update_editor(|editor, window, cx| {
 6471        editor.add_selection_below(&Default::default(), window, cx);
 6472    });
 6473
 6474    cx.assert_editor_state(indoc!(
 6475        r#"abc
 6476           def«ˇg»hi
 6477
 6478           jk
 6479           nlm«ˇo»
 6480           "#
 6481    ));
 6482
 6483    cx.update_editor(|editor, window, cx| {
 6484        editor.add_selection_above(&Default::default(), window, cx);
 6485    });
 6486
 6487    cx.assert_editor_state(indoc!(
 6488        r#"abc
 6489           def«ˇg»hi
 6490
 6491           jk
 6492           nlmo
 6493           "#
 6494    ));
 6495
 6496    cx.update_editor(|editor, window, cx| {
 6497        editor.add_selection_above(&Default::default(), window, cx);
 6498    });
 6499
 6500    cx.assert_editor_state(indoc!(
 6501        r#"abc
 6502           def«ˇg»hi
 6503
 6504           jk
 6505           nlmo
 6506           "#
 6507    ));
 6508
 6509    // Change selections again
 6510    cx.set_state(indoc!(
 6511        r#"a«bc
 6512           defgˇ»hi
 6513
 6514           jk
 6515           nlmo
 6516           "#
 6517    ));
 6518
 6519    cx.update_editor(|editor, window, cx| {
 6520        editor.add_selection_below(&Default::default(), window, cx);
 6521    });
 6522
 6523    cx.assert_editor_state(indoc!(
 6524        r#"a«bcˇ»
 6525           d«efgˇ»hi
 6526
 6527           j«kˇ»
 6528           nlmo
 6529           "#
 6530    ));
 6531
 6532    cx.update_editor(|editor, window, cx| {
 6533        editor.add_selection_below(&Default::default(), window, cx);
 6534    });
 6535    cx.assert_editor_state(indoc!(
 6536        r#"a«bcˇ»
 6537           d«efgˇ»hi
 6538
 6539           j«kˇ»
 6540           n«lmoˇ»
 6541           "#
 6542    ));
 6543    cx.update_editor(|editor, window, cx| {
 6544        editor.add_selection_above(&Default::default(), window, cx);
 6545    });
 6546
 6547    cx.assert_editor_state(indoc!(
 6548        r#"a«bcˇ»
 6549           d«efgˇ»hi
 6550
 6551           j«kˇ»
 6552           nlmo
 6553           "#
 6554    ));
 6555
 6556    // Change selections again
 6557    cx.set_state(indoc!(
 6558        r#"abc
 6559           d«ˇefghi
 6560
 6561           jk
 6562           nlm»o
 6563           "#
 6564    ));
 6565
 6566    cx.update_editor(|editor, window, cx| {
 6567        editor.add_selection_above(&Default::default(), window, cx);
 6568    });
 6569
 6570    cx.assert_editor_state(indoc!(
 6571        r#"a«ˇbc»
 6572           d«ˇef»ghi
 6573
 6574           j«ˇk»
 6575           n«ˇlm»o
 6576           "#
 6577    ));
 6578
 6579    cx.update_editor(|editor, window, cx| {
 6580        editor.add_selection_below(&Default::default(), window, cx);
 6581    });
 6582
 6583    cx.assert_editor_state(indoc!(
 6584        r#"abc
 6585           d«ˇef»ghi
 6586
 6587           j«ˇk»
 6588           n«ˇlm»o
 6589           "#
 6590    ));
 6591}
 6592
 6593#[gpui::test]
 6594async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6595    init_test(cx, |_| {});
 6596    let mut cx = EditorTestContext::new(cx).await;
 6597
 6598    cx.set_state(indoc!(
 6599        r#"line onˇe
 6600           liˇne two
 6601           line three
 6602           line four"#
 6603    ));
 6604
 6605    cx.update_editor(|editor, window, cx| {
 6606        editor.add_selection_below(&Default::default(), window, cx);
 6607    });
 6608
 6609    // test multiple cursors expand in the same direction
 6610    cx.assert_editor_state(indoc!(
 6611        r#"line onˇe
 6612           liˇne twˇo
 6613           liˇne three
 6614           line four"#
 6615    ));
 6616
 6617    cx.update_editor(|editor, window, cx| {
 6618        editor.add_selection_below(&Default::default(), window, cx);
 6619    });
 6620
 6621    cx.update_editor(|editor, window, cx| {
 6622        editor.add_selection_below(&Default::default(), window, cx);
 6623    });
 6624
 6625    // test multiple cursors expand below overflow
 6626    cx.assert_editor_state(indoc!(
 6627        r#"line onˇe
 6628           liˇne twˇo
 6629           liˇne thˇree
 6630           liˇne foˇur"#
 6631    ));
 6632
 6633    cx.update_editor(|editor, window, cx| {
 6634        editor.add_selection_above(&Default::default(), window, cx);
 6635    });
 6636
 6637    // test multiple cursors retrieves back correctly
 6638    cx.assert_editor_state(indoc!(
 6639        r#"line onˇe
 6640           liˇne twˇo
 6641           liˇne thˇree
 6642           line four"#
 6643    ));
 6644
 6645    cx.update_editor(|editor, window, cx| {
 6646        editor.add_selection_above(&Default::default(), window, cx);
 6647    });
 6648
 6649    cx.update_editor(|editor, window, cx| {
 6650        editor.add_selection_above(&Default::default(), window, cx);
 6651    });
 6652
 6653    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6654    cx.assert_editor_state(indoc!(
 6655        r#"liˇne onˇe
 6656           liˇne two
 6657           line three
 6658           line four"#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.undo_selection(&Default::default(), window, cx);
 6663    });
 6664
 6665    // test undo
 6666    cx.assert_editor_state(indoc!(
 6667        r#"line onˇe
 6668           liˇne twˇo
 6669           line three
 6670           line four"#
 6671    ));
 6672
 6673    cx.update_editor(|editor, window, cx| {
 6674        editor.redo_selection(&Default::default(), window, cx);
 6675    });
 6676
 6677    // test redo
 6678    cx.assert_editor_state(indoc!(
 6679        r#"liˇne onˇe
 6680           liˇne two
 6681           line three
 6682           line four"#
 6683    ));
 6684
 6685    cx.set_state(indoc!(
 6686        r#"abcd
 6687           ef«ghˇ»
 6688           ijkl
 6689           «mˇ»nop"#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_above(&Default::default(), window, cx);
 6694    });
 6695
 6696    // test multiple selections expand in the same direction
 6697    cx.assert_editor_state(indoc!(
 6698        r#"ab«cdˇ»
 6699           ef«ghˇ»
 6700           «iˇ»jkl
 6701           «mˇ»nop"#
 6702    ));
 6703
 6704    cx.update_editor(|editor, window, cx| {
 6705        editor.add_selection_above(&Default::default(), window, cx);
 6706    });
 6707
 6708    // test multiple selection upward overflow
 6709    cx.assert_editor_state(indoc!(
 6710        r#"ab«cdˇ»
 6711           «eˇ»f«ghˇ»
 6712           «iˇ»jkl
 6713           «mˇ»nop"#
 6714    ));
 6715
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_below(&Default::default(), window, cx);
 6718    });
 6719
 6720    // test multiple selection retrieves back correctly
 6721    cx.assert_editor_state(indoc!(
 6722        r#"abcd
 6723           ef«ghˇ»
 6724           «iˇ»jkl
 6725           «mˇ»nop"#
 6726    ));
 6727
 6728    cx.update_editor(|editor, window, cx| {
 6729        editor.add_selection_below(&Default::default(), window, cx);
 6730    });
 6731
 6732    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6733    cx.assert_editor_state(indoc!(
 6734        r#"abcd
 6735           ef«ghˇ»
 6736           ij«klˇ»
 6737           «mˇ»nop"#
 6738    ));
 6739
 6740    cx.update_editor(|editor, window, cx| {
 6741        editor.undo_selection(&Default::default(), window, cx);
 6742    });
 6743
 6744    // test undo
 6745    cx.assert_editor_state(indoc!(
 6746        r#"abcd
 6747           ef«ghˇ»
 6748           «iˇ»jkl
 6749           «mˇ»nop"#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.redo_selection(&Default::default(), window, cx);
 6754    });
 6755
 6756    // test redo
 6757    cx.assert_editor_state(indoc!(
 6758        r#"abcd
 6759           ef«ghˇ»
 6760           ij«klˇ»
 6761           «mˇ»nop"#
 6762    ));
 6763}
 6764
 6765#[gpui::test]
 6766async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6767    init_test(cx, |_| {});
 6768    let mut cx = EditorTestContext::new(cx).await;
 6769
 6770    cx.set_state(indoc!(
 6771        r#"line onˇe
 6772           liˇne two
 6773           line three
 6774           line four"#
 6775    ));
 6776
 6777    cx.update_editor(|editor, window, cx| {
 6778        editor.add_selection_below(&Default::default(), window, cx);
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780        editor.add_selection_below(&Default::default(), window, cx);
 6781    });
 6782
 6783    // initial state with two multi cursor groups
 6784    cx.assert_editor_state(indoc!(
 6785        r#"line onˇe
 6786           liˇne twˇo
 6787           liˇne thˇree
 6788           liˇne foˇur"#
 6789    ));
 6790
 6791    // add single cursor in middle - simulate opt click
 6792    cx.update_editor(|editor, window, cx| {
 6793        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6794        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6795        editor.end_selection(window, cx);
 6796    });
 6797
 6798    cx.assert_editor_state(indoc!(
 6799        r#"line onˇe
 6800           liˇne twˇo
 6801           liˇneˇ thˇree
 6802           liˇne foˇur"#
 6803    ));
 6804
 6805    cx.update_editor(|editor, window, cx| {
 6806        editor.add_selection_above(&Default::default(), window, cx);
 6807    });
 6808
 6809    // test new added selection expands above and existing selection shrinks
 6810    cx.assert_editor_state(indoc!(
 6811        r#"line onˇe
 6812           liˇneˇ twˇo
 6813           liˇneˇ thˇree
 6814           line four"#
 6815    ));
 6816
 6817    cx.update_editor(|editor, window, cx| {
 6818        editor.add_selection_above(&Default::default(), window, cx);
 6819    });
 6820
 6821    // test new added selection expands above and existing selection shrinks
 6822    cx.assert_editor_state(indoc!(
 6823        r#"lineˇ onˇe
 6824           liˇneˇ twˇo
 6825           lineˇ three
 6826           line four"#
 6827    ));
 6828
 6829    // intial state with two selection groups
 6830    cx.set_state(indoc!(
 6831        r#"abcd
 6832           ef«ghˇ»
 6833           ijkl
 6834           «mˇ»nop"#
 6835    ));
 6836
 6837    cx.update_editor(|editor, window, cx| {
 6838        editor.add_selection_above(&Default::default(), window, cx);
 6839        editor.add_selection_above(&Default::default(), window, cx);
 6840    });
 6841
 6842    cx.assert_editor_state(indoc!(
 6843        r#"ab«cdˇ»
 6844           «eˇ»f«ghˇ»
 6845           «iˇ»jkl
 6846           «mˇ»nop"#
 6847    ));
 6848
 6849    // add single selection in middle - simulate opt drag
 6850    cx.update_editor(|editor, window, cx| {
 6851        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6852        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6853        editor.update_selection(
 6854            DisplayPoint::new(DisplayRow(2), 4),
 6855            0,
 6856            gpui::Point::<f32>::default(),
 6857            window,
 6858            cx,
 6859        );
 6860        editor.end_selection(window, cx);
 6861    });
 6862
 6863    cx.assert_editor_state(indoc!(
 6864        r#"ab«cdˇ»
 6865           «eˇ»f«ghˇ»
 6866           «iˇ»jk«lˇ»
 6867           «mˇ»nop"#
 6868    ));
 6869
 6870    cx.update_editor(|editor, window, cx| {
 6871        editor.add_selection_below(&Default::default(), window, cx);
 6872    });
 6873
 6874    // test new added selection expands below, others shrinks from above
 6875    cx.assert_editor_state(indoc!(
 6876        r#"abcd
 6877           ef«ghˇ»
 6878           «iˇ»jk«lˇ»
 6879           «mˇ»no«pˇ»"#
 6880    ));
 6881}
 6882
 6883#[gpui::test]
 6884async fn test_select_next(cx: &mut TestAppContext) {
 6885    init_test(cx, |_| {});
 6886
 6887    let mut cx = EditorTestContext::new(cx).await;
 6888    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6889
 6890    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6891        .unwrap();
 6892    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6893
 6894    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6895        .unwrap();
 6896    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6897
 6898    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6899    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6900
 6901    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6902    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6903
 6904    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6905        .unwrap();
 6906    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6907
 6908    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6909        .unwrap();
 6910    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6911
 6912    // Test selection direction should be preserved
 6913    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6914
 6915    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6916        .unwrap();
 6917    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6918}
 6919
 6920#[gpui::test]
 6921async fn test_select_all_matches(cx: &mut TestAppContext) {
 6922    init_test(cx, |_| {});
 6923
 6924    let mut cx = EditorTestContext::new(cx).await;
 6925
 6926    // Test caret-only selections
 6927    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6928    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6929        .unwrap();
 6930    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6931
 6932    // Test left-to-right selections
 6933    cx.set_state("abc\n«abcˇ»\nabc");
 6934    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6935        .unwrap();
 6936    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6937
 6938    // Test right-to-left selections
 6939    cx.set_state("abc\n«ˇabc»\nabc");
 6940    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6941        .unwrap();
 6942    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6943
 6944    // Test selecting whitespace with caret selection
 6945    cx.set_state("abc\nˇ   abc\nabc");
 6946    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6947        .unwrap();
 6948    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6949
 6950    // Test selecting whitespace with left-to-right selection
 6951    cx.set_state("abc\n«ˇ  »abc\nabc");
 6952    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6953        .unwrap();
 6954    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6955
 6956    // Test no matches with right-to-left selection
 6957    cx.set_state("abc\n«  ˇ»abc\nabc");
 6958    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6959        .unwrap();
 6960    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6961
 6962    // Test with a single word and clip_at_line_ends=true (#29823)
 6963    cx.set_state("aˇbc");
 6964    cx.update_editor(|e, window, cx| {
 6965        e.set_clip_at_line_ends(true, cx);
 6966        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6967        e.set_clip_at_line_ends(false, cx);
 6968    });
 6969    cx.assert_editor_state("«abcˇ»");
 6970}
 6971
 6972#[gpui::test]
 6973async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6974    init_test(cx, |_| {});
 6975
 6976    let mut cx = EditorTestContext::new(cx).await;
 6977
 6978    let large_body_1 = "\nd".repeat(200);
 6979    let large_body_2 = "\ne".repeat(200);
 6980
 6981    cx.set_state(&format!(
 6982        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6983    ));
 6984    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6985        let scroll_position = editor.scroll_position(cx);
 6986        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6987        scroll_position
 6988    });
 6989
 6990    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6991        .unwrap();
 6992    cx.assert_editor_state(&format!(
 6993        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6994    ));
 6995    let scroll_position_after_selection =
 6996        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6997    assert_eq!(
 6998        initial_scroll_position, scroll_position_after_selection,
 6999        "Scroll position should not change after selecting all matches"
 7000    );
 7001}
 7002
 7003#[gpui::test]
 7004async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7005    init_test(cx, |_| {});
 7006
 7007    let mut cx = EditorLspTestContext::new_rust(
 7008        lsp::ServerCapabilities {
 7009            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7010            ..Default::default()
 7011        },
 7012        cx,
 7013    )
 7014    .await;
 7015
 7016    cx.set_state(indoc! {"
 7017        line 1
 7018        line 2
 7019        linˇe 3
 7020        line 4
 7021        line 5
 7022    "});
 7023
 7024    // Make an edit
 7025    cx.update_editor(|editor, window, cx| {
 7026        editor.handle_input("X", window, cx);
 7027    });
 7028
 7029    // Move cursor to a different position
 7030    cx.update_editor(|editor, window, cx| {
 7031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7032            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7033        });
 7034    });
 7035
 7036    cx.assert_editor_state(indoc! {"
 7037        line 1
 7038        line 2
 7039        linXe 3
 7040        line 4
 7041        liˇne 5
 7042    "});
 7043
 7044    cx.lsp
 7045        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7046            Ok(Some(vec![lsp::TextEdit::new(
 7047                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7048                "PREFIX ".to_string(),
 7049            )]))
 7050        });
 7051
 7052    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7053        .unwrap()
 7054        .await
 7055        .unwrap();
 7056
 7057    cx.assert_editor_state(indoc! {"
 7058        PREFIX line 1
 7059        line 2
 7060        linXe 3
 7061        line 4
 7062        liˇne 5
 7063    "});
 7064
 7065    // Undo formatting
 7066    cx.update_editor(|editor, window, cx| {
 7067        editor.undo(&Default::default(), window, cx);
 7068    });
 7069
 7070    // Verify cursor moved back to position after edit
 7071    cx.assert_editor_state(indoc! {"
 7072        line 1
 7073        line 2
 7074        linXˇe 3
 7075        line 4
 7076        line 5
 7077    "});
 7078}
 7079
 7080#[gpui::test]
 7081async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7082    init_test(cx, |_| {});
 7083
 7084    let mut cx = EditorTestContext::new(cx).await;
 7085
 7086    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7087    cx.update_editor(|editor, window, cx| {
 7088        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7089    });
 7090
 7091    cx.set_state(indoc! {"
 7092        line 1
 7093        line 2
 7094        linˇe 3
 7095        line 4
 7096        line 5
 7097        line 6
 7098        line 7
 7099        line 8
 7100        line 9
 7101        line 10
 7102    "});
 7103
 7104    let snapshot = cx.buffer_snapshot();
 7105    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7106
 7107    cx.update(|_, cx| {
 7108        provider.update(cx, |provider, _| {
 7109            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7110                id: None,
 7111                edits: vec![(edit_position..edit_position, "X".into())],
 7112                edit_preview: None,
 7113            }))
 7114        })
 7115    });
 7116
 7117    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7118    cx.update_editor(|editor, window, cx| {
 7119        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7120    });
 7121
 7122    cx.assert_editor_state(indoc! {"
 7123        line 1
 7124        line 2
 7125        lineXˇ 3
 7126        line 4
 7127        line 5
 7128        line 6
 7129        line 7
 7130        line 8
 7131        line 9
 7132        line 10
 7133    "});
 7134
 7135    cx.update_editor(|editor, window, cx| {
 7136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7137            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7138        });
 7139    });
 7140
 7141    cx.assert_editor_state(indoc! {"
 7142        line 1
 7143        line 2
 7144        lineX 3
 7145        line 4
 7146        line 5
 7147        line 6
 7148        line 7
 7149        line 8
 7150        line 9
 7151        liˇne 10
 7152    "});
 7153
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.undo(&Default::default(), window, cx);
 7156    });
 7157
 7158    cx.assert_editor_state(indoc! {"
 7159        line 1
 7160        line 2
 7161        lineˇ 3
 7162        line 4
 7163        line 5
 7164        line 6
 7165        line 7
 7166        line 8
 7167        line 9
 7168        line 10
 7169    "});
 7170}
 7171
 7172#[gpui::test]
 7173async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7174    init_test(cx, |_| {});
 7175
 7176    let mut cx = EditorTestContext::new(cx).await;
 7177    cx.set_state(
 7178        r#"let foo = 2;
 7179lˇet foo = 2;
 7180let fooˇ = 2;
 7181let foo = 2;
 7182let foo = ˇ2;"#,
 7183    );
 7184
 7185    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7186        .unwrap();
 7187    cx.assert_editor_state(
 7188        r#"let foo = 2;
 7189«letˇ» foo = 2;
 7190let «fooˇ» = 2;
 7191let foo = 2;
 7192let foo = «2ˇ»;"#,
 7193    );
 7194
 7195    // noop for multiple selections with different contents
 7196    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7197        .unwrap();
 7198    cx.assert_editor_state(
 7199        r#"let foo = 2;
 7200«letˇ» foo = 2;
 7201let «fooˇ» = 2;
 7202let foo = 2;
 7203let foo = «2ˇ»;"#,
 7204    );
 7205
 7206    // Test last selection direction should be preserved
 7207    cx.set_state(
 7208        r#"let foo = 2;
 7209let foo = 2;
 7210let «fooˇ» = 2;
 7211let «ˇfoo» = 2;
 7212let foo = 2;"#,
 7213    );
 7214
 7215    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7216        .unwrap();
 7217    cx.assert_editor_state(
 7218        r#"let foo = 2;
 7219let foo = 2;
 7220let «fooˇ» = 2;
 7221let «ˇfoo» = 2;
 7222let «ˇfoo» = 2;"#,
 7223    );
 7224}
 7225
 7226#[gpui::test]
 7227async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7228    init_test(cx, |_| {});
 7229
 7230    let mut cx =
 7231        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7232
 7233    cx.assert_editor_state(indoc! {"
 7234        ˇbbb
 7235        ccc
 7236
 7237        bbb
 7238        ccc
 7239        "});
 7240    cx.dispatch_action(SelectPrevious::default());
 7241    cx.assert_editor_state(indoc! {"
 7242                «bbbˇ»
 7243                ccc
 7244
 7245                bbb
 7246                ccc
 7247                "});
 7248    cx.dispatch_action(SelectPrevious::default());
 7249    cx.assert_editor_state(indoc! {"
 7250                «bbbˇ»
 7251                ccc
 7252
 7253                «bbbˇ»
 7254                ccc
 7255                "});
 7256}
 7257
 7258#[gpui::test]
 7259async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7260    init_test(cx, |_| {});
 7261
 7262    let mut cx = EditorTestContext::new(cx).await;
 7263    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7264
 7265    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7266        .unwrap();
 7267    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7268
 7269    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7270        .unwrap();
 7271    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7272
 7273    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7274    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7275
 7276    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7277    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7278
 7279    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7280        .unwrap();
 7281    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7282
 7283    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7284        .unwrap();
 7285    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7286}
 7287
 7288#[gpui::test]
 7289async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7290    init_test(cx, |_| {});
 7291
 7292    let mut cx = EditorTestContext::new(cx).await;
 7293    cx.set_state("");
 7294
 7295    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7296        .unwrap();
 7297    cx.assert_editor_state("«aˇ»");
 7298    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7299        .unwrap();
 7300    cx.assert_editor_state("«aˇ»");
 7301}
 7302
 7303#[gpui::test]
 7304async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7305    init_test(cx, |_| {});
 7306
 7307    let mut cx = EditorTestContext::new(cx).await;
 7308    cx.set_state(
 7309        r#"let foo = 2;
 7310lˇet foo = 2;
 7311let fooˇ = 2;
 7312let foo = 2;
 7313let foo = ˇ2;"#,
 7314    );
 7315
 7316    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7317        .unwrap();
 7318    cx.assert_editor_state(
 7319        r#"let foo = 2;
 7320«letˇ» foo = 2;
 7321let «fooˇ» = 2;
 7322let foo = 2;
 7323let foo = «2ˇ»;"#,
 7324    );
 7325
 7326    // noop for multiple selections with different contents
 7327    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7328        .unwrap();
 7329    cx.assert_editor_state(
 7330        r#"let foo = 2;
 7331«letˇ» foo = 2;
 7332let «fooˇ» = 2;
 7333let foo = 2;
 7334let foo = «2ˇ»;"#,
 7335    );
 7336}
 7337
 7338#[gpui::test]
 7339async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7340    init_test(cx, |_| {});
 7341
 7342    let mut cx = EditorTestContext::new(cx).await;
 7343    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7344
 7345    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7346        .unwrap();
 7347    // selection direction is preserved
 7348    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7349
 7350    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7351        .unwrap();
 7352    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7353
 7354    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7355    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7356
 7357    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7358    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7359
 7360    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7361        .unwrap();
 7362    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7363
 7364    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7365        .unwrap();
 7366    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7367}
 7368
 7369#[gpui::test]
 7370async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7371    init_test(cx, |_| {});
 7372
 7373    let language = Arc::new(Language::new(
 7374        LanguageConfig::default(),
 7375        Some(tree_sitter_rust::LANGUAGE.into()),
 7376    ));
 7377
 7378    let text = r#"
 7379        use mod1::mod2::{mod3, mod4};
 7380
 7381        fn fn_1(param1: bool, param2: &str) {
 7382            let var1 = "text";
 7383        }
 7384    "#
 7385    .unindent();
 7386
 7387    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7388    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7389    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7390
 7391    editor
 7392        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7393        .await;
 7394
 7395    editor.update_in(cx, |editor, window, cx| {
 7396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7397            s.select_display_ranges([
 7398                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7399                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7400                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7401            ]);
 7402        });
 7403        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7404    });
 7405    editor.update(cx, |editor, cx| {
 7406        assert_text_with_selections(
 7407            editor,
 7408            indoc! {r#"
 7409                use mod1::mod2::{mod3, «mod4ˇ»};
 7410
 7411                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7412                    let var1 = "«ˇtext»";
 7413                }
 7414            "#},
 7415            cx,
 7416        );
 7417    });
 7418
 7419    editor.update_in(cx, |editor, window, cx| {
 7420        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7421    });
 7422    editor.update(cx, |editor, cx| {
 7423        assert_text_with_selections(
 7424            editor,
 7425            indoc! {r#"
 7426                use mod1::mod2::«{mod3, mod4}ˇ»;
 7427
 7428                «ˇfn fn_1(param1: bool, param2: &str) {
 7429                    let var1 = "text";
 7430 7431            "#},
 7432            cx,
 7433        );
 7434    });
 7435
 7436    editor.update_in(cx, |editor, window, cx| {
 7437        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7438    });
 7439    assert_eq!(
 7440        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7441        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7442    );
 7443
 7444    // Trying to expand the selected syntax node one more time has no effect.
 7445    editor.update_in(cx, |editor, window, cx| {
 7446        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7447    });
 7448    assert_eq!(
 7449        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7450        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7451    );
 7452
 7453    editor.update_in(cx, |editor, window, cx| {
 7454        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7455    });
 7456    editor.update(cx, |editor, cx| {
 7457        assert_text_with_selections(
 7458            editor,
 7459            indoc! {r#"
 7460                use mod1::mod2::«{mod3, mod4}ˇ»;
 7461
 7462                «ˇfn fn_1(param1: bool, param2: &str) {
 7463                    let var1 = "text";
 7464 7465            "#},
 7466            cx,
 7467        );
 7468    });
 7469
 7470    editor.update_in(cx, |editor, window, cx| {
 7471        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7472    });
 7473    editor.update(cx, |editor, cx| {
 7474        assert_text_with_selections(
 7475            editor,
 7476            indoc! {r#"
 7477                use mod1::mod2::{mod3, «mod4ˇ»};
 7478
 7479                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7480                    let var1 = "«ˇtext»";
 7481                }
 7482            "#},
 7483            cx,
 7484        );
 7485    });
 7486
 7487    editor.update_in(cx, |editor, window, cx| {
 7488        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7489    });
 7490    editor.update(cx, |editor, cx| {
 7491        assert_text_with_selections(
 7492            editor,
 7493            indoc! {r#"
 7494                use mod1::mod2::{mod3, mo«ˇ»d4};
 7495
 7496                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7497                    let var1 = "te«ˇ»xt";
 7498                }
 7499            "#},
 7500            cx,
 7501        );
 7502    });
 7503
 7504    // Trying to shrink the selected syntax node one more time has no effect.
 7505    editor.update_in(cx, |editor, window, cx| {
 7506        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7507    });
 7508    editor.update_in(cx, |editor, _, cx| {
 7509        assert_text_with_selections(
 7510            editor,
 7511            indoc! {r#"
 7512                use mod1::mod2::{mod3, mo«ˇ»d4};
 7513
 7514                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7515                    let var1 = "te«ˇ»xt";
 7516                }
 7517            "#},
 7518            cx,
 7519        );
 7520    });
 7521
 7522    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7523    // a fold.
 7524    editor.update_in(cx, |editor, window, cx| {
 7525        editor.fold_creases(
 7526            vec![
 7527                Crease::simple(
 7528                    Point::new(0, 21)..Point::new(0, 24),
 7529                    FoldPlaceholder::test(),
 7530                ),
 7531                Crease::simple(
 7532                    Point::new(3, 20)..Point::new(3, 22),
 7533                    FoldPlaceholder::test(),
 7534                ),
 7535            ],
 7536            true,
 7537            window,
 7538            cx,
 7539        );
 7540        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7541    });
 7542    editor.update(cx, |editor, cx| {
 7543        assert_text_with_selections(
 7544            editor,
 7545            indoc! {r#"
 7546                use mod1::mod2::«{mod3, mod4}ˇ»;
 7547
 7548                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7549                    let var1 = "«ˇtext»";
 7550                }
 7551            "#},
 7552            cx,
 7553        );
 7554    });
 7555}
 7556
 7557#[gpui::test]
 7558async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7559    init_test(cx, |_| {});
 7560
 7561    let language = Arc::new(Language::new(
 7562        LanguageConfig::default(),
 7563        Some(tree_sitter_rust::LANGUAGE.into()),
 7564    ));
 7565
 7566    let text = "let a = 2;";
 7567
 7568    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7569    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7570    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7571
 7572    editor
 7573        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7574        .await;
 7575
 7576    // Test case 1: Cursor at end of word
 7577    editor.update_in(cx, |editor, window, cx| {
 7578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7579            s.select_display_ranges([
 7580                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7581            ]);
 7582        });
 7583    });
 7584    editor.update(cx, |editor, cx| {
 7585        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7586    });
 7587    editor.update_in(cx, |editor, window, cx| {
 7588        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7589    });
 7590    editor.update(cx, |editor, cx| {
 7591        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7592    });
 7593    editor.update_in(cx, |editor, window, cx| {
 7594        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7595    });
 7596    editor.update(cx, |editor, cx| {
 7597        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7598    });
 7599
 7600    // Test case 2: Cursor at end of statement
 7601    editor.update_in(cx, |editor, window, cx| {
 7602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7603            s.select_display_ranges([
 7604                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7605            ]);
 7606        });
 7607    });
 7608    editor.update(cx, |editor, cx| {
 7609        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7610    });
 7611    editor.update_in(cx, |editor, window, cx| {
 7612        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7613    });
 7614    editor.update(cx, |editor, cx| {
 7615        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7616    });
 7617}
 7618
 7619#[gpui::test]
 7620async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7621    init_test(cx, |_| {});
 7622
 7623    let language = Arc::new(Language::new(
 7624        LanguageConfig::default(),
 7625        Some(tree_sitter_rust::LANGUAGE.into()),
 7626    ));
 7627
 7628    let text = r#"
 7629        use mod1::mod2::{mod3, mod4};
 7630
 7631        fn fn_1(param1: bool, param2: &str) {
 7632            let var1 = "hello world";
 7633        }
 7634    "#
 7635    .unindent();
 7636
 7637    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7639    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7640
 7641    editor
 7642        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7643        .await;
 7644
 7645    // Test 1: Cursor on a letter of a string word
 7646    editor.update_in(cx, |editor, window, cx| {
 7647        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7648            s.select_display_ranges([
 7649                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7650            ]);
 7651        });
 7652    });
 7653    editor.update_in(cx, |editor, window, cx| {
 7654        assert_text_with_selections(
 7655            editor,
 7656            indoc! {r#"
 7657                use mod1::mod2::{mod3, mod4};
 7658
 7659                fn fn_1(param1: bool, param2: &str) {
 7660                    let var1 = "hˇello world";
 7661                }
 7662            "#},
 7663            cx,
 7664        );
 7665        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7666        assert_text_with_selections(
 7667            editor,
 7668            indoc! {r#"
 7669                use mod1::mod2::{mod3, mod4};
 7670
 7671                fn fn_1(param1: bool, param2: &str) {
 7672                    let var1 = "«ˇhello» world";
 7673                }
 7674            "#},
 7675            cx,
 7676        );
 7677    });
 7678
 7679    // Test 2: Partial selection within a word
 7680    editor.update_in(cx, |editor, window, cx| {
 7681        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7682            s.select_display_ranges([
 7683                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7684            ]);
 7685        });
 7686    });
 7687    editor.update_in(cx, |editor, window, cx| {
 7688        assert_text_with_selections(
 7689            editor,
 7690            indoc! {r#"
 7691                use mod1::mod2::{mod3, mod4};
 7692
 7693                fn fn_1(param1: bool, param2: &str) {
 7694                    let var1 = "h«elˇ»lo world";
 7695                }
 7696            "#},
 7697            cx,
 7698        );
 7699        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7700        assert_text_with_selections(
 7701            editor,
 7702            indoc! {r#"
 7703                use mod1::mod2::{mod3, mod4};
 7704
 7705                fn fn_1(param1: bool, param2: &str) {
 7706                    let var1 = "«ˇhello» world";
 7707                }
 7708            "#},
 7709            cx,
 7710        );
 7711    });
 7712
 7713    // Test 3: Complete word already selected
 7714    editor.update_in(cx, |editor, window, cx| {
 7715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7716            s.select_display_ranges([
 7717                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7718            ]);
 7719        });
 7720    });
 7721    editor.update_in(cx, |editor, window, cx| {
 7722        assert_text_with_selections(
 7723            editor,
 7724            indoc! {r#"
 7725                use mod1::mod2::{mod3, mod4};
 7726
 7727                fn fn_1(param1: bool, param2: &str) {
 7728                    let var1 = "«helloˇ» world";
 7729                }
 7730            "#},
 7731            cx,
 7732        );
 7733        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7734        assert_text_with_selections(
 7735            editor,
 7736            indoc! {r#"
 7737                use mod1::mod2::{mod3, mod4};
 7738
 7739                fn fn_1(param1: bool, param2: &str) {
 7740                    let var1 = "«hello worldˇ»";
 7741                }
 7742            "#},
 7743            cx,
 7744        );
 7745    });
 7746
 7747    // Test 4: Selection spanning across words
 7748    editor.update_in(cx, |editor, window, cx| {
 7749        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7750            s.select_display_ranges([
 7751                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7752            ]);
 7753        });
 7754    });
 7755    editor.update_in(cx, |editor, window, cx| {
 7756        assert_text_with_selections(
 7757            editor,
 7758            indoc! {r#"
 7759                use mod1::mod2::{mod3, mod4};
 7760
 7761                fn fn_1(param1: bool, param2: &str) {
 7762                    let var1 = "hel«lo woˇ»rld";
 7763                }
 7764            "#},
 7765            cx,
 7766        );
 7767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7768        assert_text_with_selections(
 7769            editor,
 7770            indoc! {r#"
 7771                use mod1::mod2::{mod3, mod4};
 7772
 7773                fn fn_1(param1: bool, param2: &str) {
 7774                    let var1 = "«ˇhello world»";
 7775                }
 7776            "#},
 7777            cx,
 7778        );
 7779    });
 7780
 7781    // Test 5: Expansion beyond string
 7782    editor.update_in(cx, |editor, window, cx| {
 7783        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7784        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7785        assert_text_with_selections(
 7786            editor,
 7787            indoc! {r#"
 7788                use mod1::mod2::{mod3, mod4};
 7789
 7790                fn fn_1(param1: bool, param2: &str) {
 7791                    «ˇlet var1 = "hello world";»
 7792                }
 7793            "#},
 7794            cx,
 7795        );
 7796    });
 7797}
 7798
 7799#[gpui::test]
 7800async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7801    init_test(cx, |_| {});
 7802
 7803    let base_text = r#"
 7804        impl A {
 7805            // this is an uncommitted comment
 7806
 7807            fn b() {
 7808                c();
 7809            }
 7810
 7811            // this is another uncommitted comment
 7812
 7813            fn d() {
 7814                // e
 7815                // f
 7816            }
 7817        }
 7818
 7819        fn g() {
 7820            // h
 7821        }
 7822    "#
 7823    .unindent();
 7824
 7825    let text = r#"
 7826        ˇimpl A {
 7827
 7828            fn b() {
 7829                c();
 7830            }
 7831
 7832            fn d() {
 7833                // e
 7834                // f
 7835            }
 7836        }
 7837
 7838        fn g() {
 7839            // h
 7840        }
 7841    "#
 7842    .unindent();
 7843
 7844    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7845    cx.set_state(&text);
 7846    cx.set_head_text(&base_text);
 7847    cx.update_editor(|editor, window, cx| {
 7848        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7849    });
 7850
 7851    cx.assert_state_with_diff(
 7852        "
 7853        ˇimpl A {
 7854      -     // this is an uncommitted comment
 7855
 7856            fn b() {
 7857                c();
 7858            }
 7859
 7860      -     // this is another uncommitted comment
 7861      -
 7862            fn d() {
 7863                // e
 7864                // f
 7865            }
 7866        }
 7867
 7868        fn g() {
 7869            // h
 7870        }
 7871    "
 7872        .unindent(),
 7873    );
 7874
 7875    let expected_display_text = "
 7876        impl A {
 7877            // this is an uncommitted comment
 7878
 7879            fn b() {
 7880 7881            }
 7882
 7883            // this is another uncommitted comment
 7884
 7885            fn d() {
 7886 7887            }
 7888        }
 7889
 7890        fn g() {
 7891 7892        }
 7893        "
 7894    .unindent();
 7895
 7896    cx.update_editor(|editor, window, cx| {
 7897        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7898        assert_eq!(editor.display_text(cx), expected_display_text);
 7899    });
 7900}
 7901
 7902#[gpui::test]
 7903async fn test_autoindent(cx: &mut TestAppContext) {
 7904    init_test(cx, |_| {});
 7905
 7906    let language = Arc::new(
 7907        Language::new(
 7908            LanguageConfig {
 7909                brackets: BracketPairConfig {
 7910                    pairs: vec![
 7911                        BracketPair {
 7912                            start: "{".to_string(),
 7913                            end: "}".to_string(),
 7914                            close: false,
 7915                            surround: false,
 7916                            newline: true,
 7917                        },
 7918                        BracketPair {
 7919                            start: "(".to_string(),
 7920                            end: ")".to_string(),
 7921                            close: false,
 7922                            surround: false,
 7923                            newline: true,
 7924                        },
 7925                    ],
 7926                    ..Default::default()
 7927                },
 7928                ..Default::default()
 7929            },
 7930            Some(tree_sitter_rust::LANGUAGE.into()),
 7931        )
 7932        .with_indents_query(
 7933            r#"
 7934                (_ "(" ")" @end) @indent
 7935                (_ "{" "}" @end) @indent
 7936            "#,
 7937        )
 7938        .unwrap(),
 7939    );
 7940
 7941    let text = "fn a() {}";
 7942
 7943    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7944    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7945    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7946    editor
 7947        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7948        .await;
 7949
 7950    editor.update_in(cx, |editor, window, cx| {
 7951        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7952            s.select_ranges([5..5, 8..8, 9..9])
 7953        });
 7954        editor.newline(&Newline, window, cx);
 7955        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7956        assert_eq!(
 7957            editor.selections.ranges(cx),
 7958            &[
 7959                Point::new(1, 4)..Point::new(1, 4),
 7960                Point::new(3, 4)..Point::new(3, 4),
 7961                Point::new(5, 0)..Point::new(5, 0)
 7962            ]
 7963        );
 7964    });
 7965}
 7966
 7967#[gpui::test]
 7968async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7969    init_test(cx, |_| {});
 7970
 7971    {
 7972        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7973        cx.set_state(indoc! {"
 7974            impl A {
 7975
 7976                fn b() {}
 7977
 7978            «fn c() {
 7979
 7980            }ˇ»
 7981            }
 7982        "});
 7983
 7984        cx.update_editor(|editor, window, cx| {
 7985            editor.autoindent(&Default::default(), window, cx);
 7986        });
 7987
 7988        cx.assert_editor_state(indoc! {"
 7989            impl A {
 7990
 7991                fn b() {}
 7992
 7993                «fn c() {
 7994
 7995                }ˇ»
 7996            }
 7997        "});
 7998    }
 7999
 8000    {
 8001        let mut cx = EditorTestContext::new_multibuffer(
 8002            cx,
 8003            [indoc! { "
 8004                impl A {
 8005                «
 8006                // a
 8007                fn b(){}
 8008                »
 8009                «
 8010                    }
 8011                    fn c(){}
 8012                »
 8013            "}],
 8014        );
 8015
 8016        let buffer = cx.update_editor(|editor, _, cx| {
 8017            let buffer = editor.buffer().update(cx, |buffer, _| {
 8018                buffer.all_buffers().iter().next().unwrap().clone()
 8019            });
 8020            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8021            buffer
 8022        });
 8023
 8024        cx.run_until_parked();
 8025        cx.update_editor(|editor, window, cx| {
 8026            editor.select_all(&Default::default(), window, cx);
 8027            editor.autoindent(&Default::default(), window, cx)
 8028        });
 8029        cx.run_until_parked();
 8030
 8031        cx.update(|_, cx| {
 8032            assert_eq!(
 8033                buffer.read(cx).text(),
 8034                indoc! { "
 8035                    impl A {
 8036
 8037                        // a
 8038                        fn b(){}
 8039
 8040
 8041                    }
 8042                    fn c(){}
 8043
 8044                " }
 8045            )
 8046        });
 8047    }
 8048}
 8049
 8050#[gpui::test]
 8051async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8052    init_test(cx, |_| {});
 8053
 8054    let mut cx = EditorTestContext::new(cx).await;
 8055
 8056    let language = Arc::new(Language::new(
 8057        LanguageConfig {
 8058            brackets: BracketPairConfig {
 8059                pairs: vec![
 8060                    BracketPair {
 8061                        start: "{".to_string(),
 8062                        end: "}".to_string(),
 8063                        close: true,
 8064                        surround: true,
 8065                        newline: true,
 8066                    },
 8067                    BracketPair {
 8068                        start: "(".to_string(),
 8069                        end: ")".to_string(),
 8070                        close: true,
 8071                        surround: true,
 8072                        newline: true,
 8073                    },
 8074                    BracketPair {
 8075                        start: "/*".to_string(),
 8076                        end: " */".to_string(),
 8077                        close: true,
 8078                        surround: true,
 8079                        newline: true,
 8080                    },
 8081                    BracketPair {
 8082                        start: "[".to_string(),
 8083                        end: "]".to_string(),
 8084                        close: false,
 8085                        surround: false,
 8086                        newline: true,
 8087                    },
 8088                    BracketPair {
 8089                        start: "\"".to_string(),
 8090                        end: "\"".to_string(),
 8091                        close: true,
 8092                        surround: true,
 8093                        newline: false,
 8094                    },
 8095                    BracketPair {
 8096                        start: "<".to_string(),
 8097                        end: ">".to_string(),
 8098                        close: false,
 8099                        surround: true,
 8100                        newline: true,
 8101                    },
 8102                ],
 8103                ..Default::default()
 8104            },
 8105            autoclose_before: "})]".to_string(),
 8106            ..Default::default()
 8107        },
 8108        Some(tree_sitter_rust::LANGUAGE.into()),
 8109    ));
 8110
 8111    cx.language_registry().add(language.clone());
 8112    cx.update_buffer(|buffer, cx| {
 8113        buffer.set_language(Some(language), cx);
 8114    });
 8115
 8116    cx.set_state(
 8117        &r#"
 8118            🏀ˇ
 8119            εˇ
 8120            ❤️ˇ
 8121        "#
 8122        .unindent(),
 8123    );
 8124
 8125    // autoclose multiple nested brackets at multiple cursors
 8126    cx.update_editor(|editor, window, cx| {
 8127        editor.handle_input("{", window, cx);
 8128        editor.handle_input("{", window, cx);
 8129        editor.handle_input("{", window, cx);
 8130    });
 8131    cx.assert_editor_state(
 8132        &"
 8133            🏀{{{ˇ}}}
 8134            ε{{{ˇ}}}
 8135            ❤️{{{ˇ}}}
 8136        "
 8137        .unindent(),
 8138    );
 8139
 8140    // insert a different closing bracket
 8141    cx.update_editor(|editor, window, cx| {
 8142        editor.handle_input(")", window, cx);
 8143    });
 8144    cx.assert_editor_state(
 8145        &"
 8146            🏀{{{)ˇ}}}
 8147            ε{{{)ˇ}}}
 8148            ❤️{{{)ˇ}}}
 8149        "
 8150        .unindent(),
 8151    );
 8152
 8153    // skip over the auto-closed brackets when typing a closing bracket
 8154    cx.update_editor(|editor, window, cx| {
 8155        editor.move_right(&MoveRight, window, cx);
 8156        editor.handle_input("}", window, cx);
 8157        editor.handle_input("}", window, cx);
 8158        editor.handle_input("}", window, cx);
 8159    });
 8160    cx.assert_editor_state(
 8161        &"
 8162            🏀{{{)}}}}ˇ
 8163            ε{{{)}}}}ˇ
 8164            ❤️{{{)}}}}ˇ
 8165        "
 8166        .unindent(),
 8167    );
 8168
 8169    // autoclose multi-character pairs
 8170    cx.set_state(
 8171        &"
 8172            ˇ
 8173            ˇ
 8174        "
 8175        .unindent(),
 8176    );
 8177    cx.update_editor(|editor, window, cx| {
 8178        editor.handle_input("/", window, cx);
 8179        editor.handle_input("*", window, cx);
 8180    });
 8181    cx.assert_editor_state(
 8182        &"
 8183            /*ˇ */
 8184            /*ˇ */
 8185        "
 8186        .unindent(),
 8187    );
 8188
 8189    // one cursor autocloses a multi-character pair, one cursor
 8190    // does not autoclose.
 8191    cx.set_state(
 8192        &"
 8193 8194            ˇ
 8195        "
 8196        .unindent(),
 8197    );
 8198    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8199    cx.assert_editor_state(
 8200        &"
 8201            /*ˇ */
 8202 8203        "
 8204        .unindent(),
 8205    );
 8206
 8207    // Don't autoclose if the next character isn't whitespace and isn't
 8208    // listed in the language's "autoclose_before" section.
 8209    cx.set_state("ˇa b");
 8210    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8211    cx.assert_editor_state("{ˇa b");
 8212
 8213    // Don't autoclose if `close` is false for the bracket pair
 8214    cx.set_state("ˇ");
 8215    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8216    cx.assert_editor_state("");
 8217
 8218    // Surround with brackets if text is selected
 8219    cx.set_state("«aˇ» b");
 8220    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8221    cx.assert_editor_state("{«aˇ»} b");
 8222
 8223    // Autoclose when not immediately after a word character
 8224    cx.set_state("a ˇ");
 8225    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8226    cx.assert_editor_state("a \"ˇ\"");
 8227
 8228    // Autoclose pair where the start and end characters are the same
 8229    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8230    cx.assert_editor_state("a \"\"ˇ");
 8231
 8232    // Don't autoclose when immediately after a word character
 8233    cx.set_state("");
 8234    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8235    cx.assert_editor_state("a\"ˇ");
 8236
 8237    // Do autoclose when after a non-word character
 8238    cx.set_state("");
 8239    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8240    cx.assert_editor_state("{\"ˇ\"");
 8241
 8242    // Non identical pairs autoclose regardless of preceding character
 8243    cx.set_state("");
 8244    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8245    cx.assert_editor_state("a{ˇ}");
 8246
 8247    // Don't autoclose pair if autoclose is disabled
 8248    cx.set_state("ˇ");
 8249    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8250    cx.assert_editor_state("");
 8251
 8252    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8253    cx.set_state("«aˇ» b");
 8254    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8255    cx.assert_editor_state("<«aˇ»> b");
 8256}
 8257
 8258#[gpui::test]
 8259async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8260    init_test(cx, |settings| {
 8261        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8262    });
 8263
 8264    let mut cx = EditorTestContext::new(cx).await;
 8265
 8266    let language = Arc::new(Language::new(
 8267        LanguageConfig {
 8268            brackets: BracketPairConfig {
 8269                pairs: vec![
 8270                    BracketPair {
 8271                        start: "{".to_string(),
 8272                        end: "}".to_string(),
 8273                        close: true,
 8274                        surround: true,
 8275                        newline: true,
 8276                    },
 8277                    BracketPair {
 8278                        start: "(".to_string(),
 8279                        end: ")".to_string(),
 8280                        close: true,
 8281                        surround: true,
 8282                        newline: true,
 8283                    },
 8284                    BracketPair {
 8285                        start: "[".to_string(),
 8286                        end: "]".to_string(),
 8287                        close: false,
 8288                        surround: false,
 8289                        newline: true,
 8290                    },
 8291                ],
 8292                ..Default::default()
 8293            },
 8294            autoclose_before: "})]".to_string(),
 8295            ..Default::default()
 8296        },
 8297        Some(tree_sitter_rust::LANGUAGE.into()),
 8298    ));
 8299
 8300    cx.language_registry().add(language.clone());
 8301    cx.update_buffer(|buffer, cx| {
 8302        buffer.set_language(Some(language), cx);
 8303    });
 8304
 8305    cx.set_state(
 8306        &"
 8307            ˇ
 8308            ˇ
 8309            ˇ
 8310        "
 8311        .unindent(),
 8312    );
 8313
 8314    // ensure only matching closing brackets are skipped over
 8315    cx.update_editor(|editor, window, cx| {
 8316        editor.handle_input("}", window, cx);
 8317        editor.move_left(&MoveLeft, window, cx);
 8318        editor.handle_input(")", window, cx);
 8319        editor.move_left(&MoveLeft, window, cx);
 8320    });
 8321    cx.assert_editor_state(
 8322        &"
 8323            ˇ)}
 8324            ˇ)}
 8325            ˇ)}
 8326        "
 8327        .unindent(),
 8328    );
 8329
 8330    // skip-over closing brackets at multiple cursors
 8331    cx.update_editor(|editor, window, cx| {
 8332        editor.handle_input(")", window, cx);
 8333        editor.handle_input("}", window, cx);
 8334    });
 8335    cx.assert_editor_state(
 8336        &"
 8337            )}ˇ
 8338            )}ˇ
 8339            )}ˇ
 8340        "
 8341        .unindent(),
 8342    );
 8343
 8344    // ignore non-close brackets
 8345    cx.update_editor(|editor, window, cx| {
 8346        editor.handle_input("]", window, cx);
 8347        editor.move_left(&MoveLeft, window, cx);
 8348        editor.handle_input("]", window, cx);
 8349    });
 8350    cx.assert_editor_state(
 8351        &"
 8352            )}]ˇ]
 8353            )}]ˇ]
 8354            )}]ˇ]
 8355        "
 8356        .unindent(),
 8357    );
 8358}
 8359
 8360#[gpui::test]
 8361async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8362    init_test(cx, |_| {});
 8363
 8364    let mut cx = EditorTestContext::new(cx).await;
 8365
 8366    let html_language = Arc::new(
 8367        Language::new(
 8368            LanguageConfig {
 8369                name: "HTML".into(),
 8370                brackets: BracketPairConfig {
 8371                    pairs: vec![
 8372                        BracketPair {
 8373                            start: "<".into(),
 8374                            end: ">".into(),
 8375                            close: true,
 8376                            ..Default::default()
 8377                        },
 8378                        BracketPair {
 8379                            start: "{".into(),
 8380                            end: "}".into(),
 8381                            close: true,
 8382                            ..Default::default()
 8383                        },
 8384                        BracketPair {
 8385                            start: "(".into(),
 8386                            end: ")".into(),
 8387                            close: true,
 8388                            ..Default::default()
 8389                        },
 8390                    ],
 8391                    ..Default::default()
 8392                },
 8393                autoclose_before: "})]>".into(),
 8394                ..Default::default()
 8395            },
 8396            Some(tree_sitter_html::LANGUAGE.into()),
 8397        )
 8398        .with_injection_query(
 8399            r#"
 8400            (script_element
 8401                (raw_text) @injection.content
 8402                (#set! injection.language "javascript"))
 8403            "#,
 8404        )
 8405        .unwrap(),
 8406    );
 8407
 8408    let javascript_language = Arc::new(Language::new(
 8409        LanguageConfig {
 8410            name: "JavaScript".into(),
 8411            brackets: BracketPairConfig {
 8412                pairs: vec![
 8413                    BracketPair {
 8414                        start: "/*".into(),
 8415                        end: " */".into(),
 8416                        close: true,
 8417                        ..Default::default()
 8418                    },
 8419                    BracketPair {
 8420                        start: "{".into(),
 8421                        end: "}".into(),
 8422                        close: true,
 8423                        ..Default::default()
 8424                    },
 8425                    BracketPair {
 8426                        start: "(".into(),
 8427                        end: ")".into(),
 8428                        close: true,
 8429                        ..Default::default()
 8430                    },
 8431                ],
 8432                ..Default::default()
 8433            },
 8434            autoclose_before: "})]>".into(),
 8435            ..Default::default()
 8436        },
 8437        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8438    ));
 8439
 8440    cx.language_registry().add(html_language.clone());
 8441    cx.language_registry().add(javascript_language.clone());
 8442
 8443    cx.update_buffer(|buffer, cx| {
 8444        buffer.set_language(Some(html_language), cx);
 8445    });
 8446
 8447    cx.set_state(
 8448        &r#"
 8449            <body>ˇ
 8450                <script>
 8451                    var x = 1;ˇ
 8452                </script>
 8453            </body>ˇ
 8454        "#
 8455        .unindent(),
 8456    );
 8457
 8458    // Precondition: different languages are active at different locations.
 8459    cx.update_editor(|editor, window, cx| {
 8460        let snapshot = editor.snapshot(window, cx);
 8461        let cursors = editor.selections.ranges::<usize>(cx);
 8462        let languages = cursors
 8463            .iter()
 8464            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8465            .collect::<Vec<_>>();
 8466        assert_eq!(
 8467            languages,
 8468            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8469        );
 8470    });
 8471
 8472    // Angle brackets autoclose in HTML, but not JavaScript.
 8473    cx.update_editor(|editor, window, cx| {
 8474        editor.handle_input("<", window, cx);
 8475        editor.handle_input("a", window, cx);
 8476    });
 8477    cx.assert_editor_state(
 8478        &r#"
 8479            <body><aˇ>
 8480                <script>
 8481                    var x = 1;<aˇ
 8482                </script>
 8483            </body><aˇ>
 8484        "#
 8485        .unindent(),
 8486    );
 8487
 8488    // Curly braces and parens autoclose in both HTML and JavaScript.
 8489    cx.update_editor(|editor, window, cx| {
 8490        editor.handle_input(" b=", window, cx);
 8491        editor.handle_input("{", window, cx);
 8492        editor.handle_input("c", window, cx);
 8493        editor.handle_input("(", window, cx);
 8494    });
 8495    cx.assert_editor_state(
 8496        &r#"
 8497            <body><a b={c(ˇ)}>
 8498                <script>
 8499                    var x = 1;<a b={c(ˇ)}
 8500                </script>
 8501            </body><a b={c(ˇ)}>
 8502        "#
 8503        .unindent(),
 8504    );
 8505
 8506    // Brackets that were already autoclosed are skipped.
 8507    cx.update_editor(|editor, window, cx| {
 8508        editor.handle_input(")", window, cx);
 8509        editor.handle_input("d", window, cx);
 8510        editor.handle_input("}", window, cx);
 8511    });
 8512    cx.assert_editor_state(
 8513        &r#"
 8514            <body><a b={c()d}ˇ>
 8515                <script>
 8516                    var x = 1;<a b={c()d}ˇ
 8517                </script>
 8518            </body><a b={c()d}ˇ>
 8519        "#
 8520        .unindent(),
 8521    );
 8522    cx.update_editor(|editor, window, cx| {
 8523        editor.handle_input(">", window, cx);
 8524    });
 8525    cx.assert_editor_state(
 8526        &r#"
 8527            <body><a b={c()d}>ˇ
 8528                <script>
 8529                    var x = 1;<a b={c()d}>ˇ
 8530                </script>
 8531            </body><a b={c()d}>ˇ
 8532        "#
 8533        .unindent(),
 8534    );
 8535
 8536    // Reset
 8537    cx.set_state(
 8538        &r#"
 8539            <body>ˇ
 8540                <script>
 8541                    var x = 1;ˇ
 8542                </script>
 8543            </body>ˇ
 8544        "#
 8545        .unindent(),
 8546    );
 8547
 8548    cx.update_editor(|editor, window, cx| {
 8549        editor.handle_input("<", window, cx);
 8550    });
 8551    cx.assert_editor_state(
 8552        &r#"
 8553            <body><ˇ>
 8554                <script>
 8555                    var x = 1;<ˇ
 8556                </script>
 8557            </body><ˇ>
 8558        "#
 8559        .unindent(),
 8560    );
 8561
 8562    // When backspacing, the closing angle brackets are removed.
 8563    cx.update_editor(|editor, window, cx| {
 8564        editor.backspace(&Backspace, window, cx);
 8565    });
 8566    cx.assert_editor_state(
 8567        &r#"
 8568            <body>ˇ
 8569                <script>
 8570                    var x = 1;ˇ
 8571                </script>
 8572            </body>ˇ
 8573        "#
 8574        .unindent(),
 8575    );
 8576
 8577    // Block comments autoclose in JavaScript, but not HTML.
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.handle_input("/", window, cx);
 8580        editor.handle_input("*", window, cx);
 8581    });
 8582    cx.assert_editor_state(
 8583        &r#"
 8584            <body>/*ˇ
 8585                <script>
 8586                    var x = 1;/*ˇ */
 8587                </script>
 8588            </body>/*ˇ
 8589        "#
 8590        .unindent(),
 8591    );
 8592}
 8593
 8594#[gpui::test]
 8595async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8596    init_test(cx, |_| {});
 8597
 8598    let mut cx = EditorTestContext::new(cx).await;
 8599
 8600    let rust_language = Arc::new(
 8601        Language::new(
 8602            LanguageConfig {
 8603                name: "Rust".into(),
 8604                brackets: serde_json::from_value(json!([
 8605                    { "start": "{", "end": "}", "close": true, "newline": true },
 8606                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8607                ]))
 8608                .unwrap(),
 8609                autoclose_before: "})]>".into(),
 8610                ..Default::default()
 8611            },
 8612            Some(tree_sitter_rust::LANGUAGE.into()),
 8613        )
 8614        .with_override_query("(string_literal) @string")
 8615        .unwrap(),
 8616    );
 8617
 8618    cx.language_registry().add(rust_language.clone());
 8619    cx.update_buffer(|buffer, cx| {
 8620        buffer.set_language(Some(rust_language), cx);
 8621    });
 8622
 8623    cx.set_state(
 8624        &r#"
 8625            let x = ˇ
 8626        "#
 8627        .unindent(),
 8628    );
 8629
 8630    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8631    cx.update_editor(|editor, window, cx| {
 8632        editor.handle_input("\"", window, cx);
 8633    });
 8634    cx.assert_editor_state(
 8635        &r#"
 8636            let x = "ˇ"
 8637        "#
 8638        .unindent(),
 8639    );
 8640
 8641    // Inserting another quotation mark. The cursor moves across the existing
 8642    // automatically-inserted quotation mark.
 8643    cx.update_editor(|editor, window, cx| {
 8644        editor.handle_input("\"", window, cx);
 8645    });
 8646    cx.assert_editor_state(
 8647        &r#"
 8648            let x = ""ˇ
 8649        "#
 8650        .unindent(),
 8651    );
 8652
 8653    // Reset
 8654    cx.set_state(
 8655        &r#"
 8656            let x = ˇ
 8657        "#
 8658        .unindent(),
 8659    );
 8660
 8661    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8662    cx.update_editor(|editor, window, cx| {
 8663        editor.handle_input("\"", window, cx);
 8664        editor.handle_input(" ", window, cx);
 8665        editor.move_left(&Default::default(), window, cx);
 8666        editor.handle_input("\\", window, cx);
 8667        editor.handle_input("\"", window, cx);
 8668    });
 8669    cx.assert_editor_state(
 8670        &r#"
 8671            let x = "\"ˇ "
 8672        "#
 8673        .unindent(),
 8674    );
 8675
 8676    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8677    // mark. Nothing is inserted.
 8678    cx.update_editor(|editor, window, cx| {
 8679        editor.move_right(&Default::default(), window, cx);
 8680        editor.handle_input("\"", window, cx);
 8681    });
 8682    cx.assert_editor_state(
 8683        &r#"
 8684            let x = "\" "ˇ
 8685        "#
 8686        .unindent(),
 8687    );
 8688}
 8689
 8690#[gpui::test]
 8691async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8692    init_test(cx, |_| {});
 8693
 8694    let language = Arc::new(Language::new(
 8695        LanguageConfig {
 8696            brackets: BracketPairConfig {
 8697                pairs: vec![
 8698                    BracketPair {
 8699                        start: "{".to_string(),
 8700                        end: "}".to_string(),
 8701                        close: true,
 8702                        surround: true,
 8703                        newline: true,
 8704                    },
 8705                    BracketPair {
 8706                        start: "/* ".to_string(),
 8707                        end: "*/".to_string(),
 8708                        close: true,
 8709                        surround: true,
 8710                        ..Default::default()
 8711                    },
 8712                ],
 8713                ..Default::default()
 8714            },
 8715            ..Default::default()
 8716        },
 8717        Some(tree_sitter_rust::LANGUAGE.into()),
 8718    ));
 8719
 8720    let text = r#"
 8721        a
 8722        b
 8723        c
 8724    "#
 8725    .unindent();
 8726
 8727    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8728    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8729    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8730    editor
 8731        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8732        .await;
 8733
 8734    editor.update_in(cx, |editor, window, cx| {
 8735        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8736            s.select_display_ranges([
 8737                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8738                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8739                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8740            ])
 8741        });
 8742
 8743        editor.handle_input("{", window, cx);
 8744        editor.handle_input("{", window, cx);
 8745        editor.handle_input("{", window, cx);
 8746        assert_eq!(
 8747            editor.text(cx),
 8748            "
 8749                {{{a}}}
 8750                {{{b}}}
 8751                {{{c}}}
 8752            "
 8753            .unindent()
 8754        );
 8755        assert_eq!(
 8756            editor.selections.display_ranges(cx),
 8757            [
 8758                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8759                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8760                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8761            ]
 8762        );
 8763
 8764        editor.undo(&Undo, window, cx);
 8765        editor.undo(&Undo, window, cx);
 8766        editor.undo(&Undo, window, cx);
 8767        assert_eq!(
 8768            editor.text(cx),
 8769            "
 8770                a
 8771                b
 8772                c
 8773            "
 8774            .unindent()
 8775        );
 8776        assert_eq!(
 8777            editor.selections.display_ranges(cx),
 8778            [
 8779                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8780                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8781                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8782            ]
 8783        );
 8784
 8785        // Ensure inserting the first character of a multi-byte bracket pair
 8786        // doesn't surround the selections with the bracket.
 8787        editor.handle_input("/", window, cx);
 8788        assert_eq!(
 8789            editor.text(cx),
 8790            "
 8791                /
 8792                /
 8793                /
 8794            "
 8795            .unindent()
 8796        );
 8797        assert_eq!(
 8798            editor.selections.display_ranges(cx),
 8799            [
 8800                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8801                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8802                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8803            ]
 8804        );
 8805
 8806        editor.undo(&Undo, window, cx);
 8807        assert_eq!(
 8808            editor.text(cx),
 8809            "
 8810                a
 8811                b
 8812                c
 8813            "
 8814            .unindent()
 8815        );
 8816        assert_eq!(
 8817            editor.selections.display_ranges(cx),
 8818            [
 8819                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8820                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8821                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8822            ]
 8823        );
 8824
 8825        // Ensure inserting the last character of a multi-byte bracket pair
 8826        // doesn't surround the selections with the bracket.
 8827        editor.handle_input("*", window, cx);
 8828        assert_eq!(
 8829            editor.text(cx),
 8830            "
 8831                *
 8832                *
 8833                *
 8834            "
 8835            .unindent()
 8836        );
 8837        assert_eq!(
 8838            editor.selections.display_ranges(cx),
 8839            [
 8840                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8841                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8842                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8843            ]
 8844        );
 8845    });
 8846}
 8847
 8848#[gpui::test]
 8849async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8850    init_test(cx, |_| {});
 8851
 8852    let language = Arc::new(Language::new(
 8853        LanguageConfig {
 8854            brackets: BracketPairConfig {
 8855                pairs: vec![BracketPair {
 8856                    start: "{".to_string(),
 8857                    end: "}".to_string(),
 8858                    close: true,
 8859                    surround: true,
 8860                    newline: true,
 8861                }],
 8862                ..Default::default()
 8863            },
 8864            autoclose_before: "}".to_string(),
 8865            ..Default::default()
 8866        },
 8867        Some(tree_sitter_rust::LANGUAGE.into()),
 8868    ));
 8869
 8870    let text = r#"
 8871        a
 8872        b
 8873        c
 8874    "#
 8875    .unindent();
 8876
 8877    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8878    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8879    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8880    editor
 8881        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8882        .await;
 8883
 8884    editor.update_in(cx, |editor, window, cx| {
 8885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8886            s.select_ranges([
 8887                Point::new(0, 1)..Point::new(0, 1),
 8888                Point::new(1, 1)..Point::new(1, 1),
 8889                Point::new(2, 1)..Point::new(2, 1),
 8890            ])
 8891        });
 8892
 8893        editor.handle_input("{", window, cx);
 8894        editor.handle_input("{", window, cx);
 8895        editor.handle_input("_", window, cx);
 8896        assert_eq!(
 8897            editor.text(cx),
 8898            "
 8899                a{{_}}
 8900                b{{_}}
 8901                c{{_}}
 8902            "
 8903            .unindent()
 8904        );
 8905        assert_eq!(
 8906            editor.selections.ranges::<Point>(cx),
 8907            [
 8908                Point::new(0, 4)..Point::new(0, 4),
 8909                Point::new(1, 4)..Point::new(1, 4),
 8910                Point::new(2, 4)..Point::new(2, 4)
 8911            ]
 8912        );
 8913
 8914        editor.backspace(&Default::default(), window, cx);
 8915        editor.backspace(&Default::default(), window, cx);
 8916        assert_eq!(
 8917            editor.text(cx),
 8918            "
 8919                a{}
 8920                b{}
 8921                c{}
 8922            "
 8923            .unindent()
 8924        );
 8925        assert_eq!(
 8926            editor.selections.ranges::<Point>(cx),
 8927            [
 8928                Point::new(0, 2)..Point::new(0, 2),
 8929                Point::new(1, 2)..Point::new(1, 2),
 8930                Point::new(2, 2)..Point::new(2, 2)
 8931            ]
 8932        );
 8933
 8934        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8935        assert_eq!(
 8936            editor.text(cx),
 8937            "
 8938                a
 8939                b
 8940                c
 8941            "
 8942            .unindent()
 8943        );
 8944        assert_eq!(
 8945            editor.selections.ranges::<Point>(cx),
 8946            [
 8947                Point::new(0, 1)..Point::new(0, 1),
 8948                Point::new(1, 1)..Point::new(1, 1),
 8949                Point::new(2, 1)..Point::new(2, 1)
 8950            ]
 8951        );
 8952    });
 8953}
 8954
 8955#[gpui::test]
 8956async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8957    init_test(cx, |settings| {
 8958        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8959    });
 8960
 8961    let mut cx = EditorTestContext::new(cx).await;
 8962
 8963    let language = Arc::new(Language::new(
 8964        LanguageConfig {
 8965            brackets: BracketPairConfig {
 8966                pairs: vec![
 8967                    BracketPair {
 8968                        start: "{".to_string(),
 8969                        end: "}".to_string(),
 8970                        close: true,
 8971                        surround: true,
 8972                        newline: true,
 8973                    },
 8974                    BracketPair {
 8975                        start: "(".to_string(),
 8976                        end: ")".to_string(),
 8977                        close: true,
 8978                        surround: true,
 8979                        newline: true,
 8980                    },
 8981                    BracketPair {
 8982                        start: "[".to_string(),
 8983                        end: "]".to_string(),
 8984                        close: false,
 8985                        surround: true,
 8986                        newline: true,
 8987                    },
 8988                ],
 8989                ..Default::default()
 8990            },
 8991            autoclose_before: "})]".to_string(),
 8992            ..Default::default()
 8993        },
 8994        Some(tree_sitter_rust::LANGUAGE.into()),
 8995    ));
 8996
 8997    cx.language_registry().add(language.clone());
 8998    cx.update_buffer(|buffer, cx| {
 8999        buffer.set_language(Some(language), cx);
 9000    });
 9001
 9002    cx.set_state(
 9003        &"
 9004            {(ˇ)}
 9005            [[ˇ]]
 9006            {(ˇ)}
 9007        "
 9008        .unindent(),
 9009    );
 9010
 9011    cx.update_editor(|editor, window, cx| {
 9012        editor.backspace(&Default::default(), window, cx);
 9013        editor.backspace(&Default::default(), window, cx);
 9014    });
 9015
 9016    cx.assert_editor_state(
 9017        &"
 9018            ˇ
 9019            ˇ]]
 9020            ˇ
 9021        "
 9022        .unindent(),
 9023    );
 9024
 9025    cx.update_editor(|editor, window, cx| {
 9026        editor.handle_input("{", window, cx);
 9027        editor.handle_input("{", window, cx);
 9028        editor.move_right(&MoveRight, window, cx);
 9029        editor.move_right(&MoveRight, window, cx);
 9030        editor.move_left(&MoveLeft, window, cx);
 9031        editor.move_left(&MoveLeft, window, cx);
 9032        editor.backspace(&Default::default(), window, cx);
 9033    });
 9034
 9035    cx.assert_editor_state(
 9036        &"
 9037            {ˇ}
 9038            {ˇ}]]
 9039            {ˇ}
 9040        "
 9041        .unindent(),
 9042    );
 9043
 9044    cx.update_editor(|editor, window, cx| {
 9045        editor.backspace(&Default::default(), window, cx);
 9046    });
 9047
 9048    cx.assert_editor_state(
 9049        &"
 9050            ˇ
 9051            ˇ]]
 9052            ˇ
 9053        "
 9054        .unindent(),
 9055    );
 9056}
 9057
 9058#[gpui::test]
 9059async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9060    init_test(cx, |_| {});
 9061
 9062    let language = Arc::new(Language::new(
 9063        LanguageConfig::default(),
 9064        Some(tree_sitter_rust::LANGUAGE.into()),
 9065    ));
 9066
 9067    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9069    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9070    editor
 9071        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9072        .await;
 9073
 9074    editor.update_in(cx, |editor, window, cx| {
 9075        editor.set_auto_replace_emoji_shortcode(true);
 9076
 9077        editor.handle_input("Hello ", window, cx);
 9078        editor.handle_input(":wave", window, cx);
 9079        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9080
 9081        editor.handle_input(":", window, cx);
 9082        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9083
 9084        editor.handle_input(" :smile", window, cx);
 9085        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9086
 9087        editor.handle_input(":", window, cx);
 9088        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9089
 9090        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9091        editor.handle_input(":wave", window, cx);
 9092        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9093
 9094        editor.handle_input(":", window, cx);
 9095        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9096
 9097        editor.handle_input(":1", window, cx);
 9098        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9099
 9100        editor.handle_input(":", window, cx);
 9101        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9102
 9103        // Ensure shortcode does not get replaced when it is part of a word
 9104        editor.handle_input(" Test:wave", window, cx);
 9105        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9106
 9107        editor.handle_input(":", window, cx);
 9108        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9109
 9110        editor.set_auto_replace_emoji_shortcode(false);
 9111
 9112        // Ensure shortcode does not get replaced when auto replace is off
 9113        editor.handle_input(" :wave", window, cx);
 9114        assert_eq!(
 9115            editor.text(cx),
 9116            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9117        );
 9118
 9119        editor.handle_input(":", window, cx);
 9120        assert_eq!(
 9121            editor.text(cx),
 9122            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9123        );
 9124    });
 9125}
 9126
 9127#[gpui::test]
 9128async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9129    init_test(cx, |_| {});
 9130
 9131    let (text, insertion_ranges) = marked_text_ranges(
 9132        indoc! {"
 9133            ˇ
 9134        "},
 9135        false,
 9136    );
 9137
 9138    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9139    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9140
 9141    _ = editor.update_in(cx, |editor, window, cx| {
 9142        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9143
 9144        editor
 9145            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9146            .unwrap();
 9147
 9148        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9149            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9150            assert_eq!(editor.text(cx), expected_text);
 9151            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9152        }
 9153
 9154        assert(
 9155            editor,
 9156            cx,
 9157            indoc! {"
 9158            type «» =•
 9159            "},
 9160        );
 9161
 9162        assert!(editor.context_menu_visible(), "There should be a matches");
 9163    });
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_snippets(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let mut cx = EditorTestContext::new(cx).await;
 9171
 9172    cx.set_state(indoc! {"
 9173        a.ˇ b
 9174        a.ˇ b
 9175        a.ˇ b
 9176    "});
 9177
 9178    cx.update_editor(|editor, window, cx| {
 9179        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9180        let insertion_ranges = editor
 9181            .selections
 9182            .all(cx)
 9183            .iter()
 9184            .map(|s| s.range().clone())
 9185            .collect::<Vec<_>>();
 9186        editor
 9187            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9188            .unwrap();
 9189    });
 9190
 9191    cx.assert_editor_state(indoc! {"
 9192        a.f(«oneˇ», two, «threeˇ») b
 9193        a.f(«oneˇ», two, «threeˇ») b
 9194        a.f(«oneˇ», two, «threeˇ») b
 9195    "});
 9196
 9197    // Can't move earlier than the first tab stop
 9198    cx.update_editor(|editor, window, cx| {
 9199        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9200    });
 9201    cx.assert_editor_state(indoc! {"
 9202        a.f(«oneˇ», two, «threeˇ») b
 9203        a.f(«oneˇ», two, «threeˇ») b
 9204        a.f(«oneˇ», two, «threeˇ») b
 9205    "});
 9206
 9207    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9208    cx.assert_editor_state(indoc! {"
 9209        a.f(one, «twoˇ», three) b
 9210        a.f(one, «twoˇ», three) b
 9211        a.f(one, «twoˇ», three) b
 9212    "});
 9213
 9214    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9215    cx.assert_editor_state(indoc! {"
 9216        a.f(«oneˇ», two, «threeˇ») b
 9217        a.f(«oneˇ», two, «threeˇ») b
 9218        a.f(«oneˇ», two, «threeˇ») b
 9219    "});
 9220
 9221    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9222    cx.assert_editor_state(indoc! {"
 9223        a.f(one, «twoˇ», three) b
 9224        a.f(one, «twoˇ», three) b
 9225        a.f(one, «twoˇ», three) b
 9226    "});
 9227    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9228    cx.assert_editor_state(indoc! {"
 9229        a.f(one, two, three)ˇ b
 9230        a.f(one, two, three)ˇ b
 9231        a.f(one, two, three)ˇ b
 9232    "});
 9233
 9234    // As soon as the last tab stop is reached, snippet state is gone
 9235    cx.update_editor(|editor, window, cx| {
 9236        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9237    });
 9238    cx.assert_editor_state(indoc! {"
 9239        a.f(one, two, three)ˇ b
 9240        a.f(one, two, three)ˇ b
 9241        a.f(one, two, three)ˇ b
 9242    "});
 9243}
 9244
 9245#[gpui::test]
 9246async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9247    init_test(cx, |_| {});
 9248
 9249    let mut cx = EditorTestContext::new(cx).await;
 9250
 9251    cx.update_editor(|editor, window, cx| {
 9252        let snippet = Snippet::parse(indoc! {"
 9253            /*
 9254             * Multiline comment with leading indentation
 9255             *
 9256             * $1
 9257             */
 9258            $0"})
 9259        .unwrap();
 9260        let insertion_ranges = editor
 9261            .selections
 9262            .all(cx)
 9263            .iter()
 9264            .map(|s| s.range().clone())
 9265            .collect::<Vec<_>>();
 9266        editor
 9267            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9268            .unwrap();
 9269    });
 9270
 9271    cx.assert_editor_state(indoc! {"
 9272        /*
 9273         * Multiline comment with leading indentation
 9274         *
 9275         * ˇ
 9276         */
 9277    "});
 9278
 9279    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9280    cx.assert_editor_state(indoc! {"
 9281        /*
 9282         * Multiline comment with leading indentation
 9283         *
 9284         *•
 9285         */
 9286        ˇ"});
 9287}
 9288
 9289#[gpui::test]
 9290async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9291    init_test(cx, |_| {});
 9292
 9293    let fs = FakeFs::new(cx.executor());
 9294    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9295
 9296    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9297
 9298    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9299    language_registry.add(rust_lang());
 9300    let mut fake_servers = language_registry.register_fake_lsp(
 9301        "Rust",
 9302        FakeLspAdapter {
 9303            capabilities: lsp::ServerCapabilities {
 9304                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9305                ..Default::default()
 9306            },
 9307            ..Default::default()
 9308        },
 9309    );
 9310
 9311    let buffer = project
 9312        .update(cx, |project, cx| {
 9313            project.open_local_buffer(path!("/file.rs"), cx)
 9314        })
 9315        .await
 9316        .unwrap();
 9317
 9318    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9319    let (editor, cx) = cx.add_window_view(|window, cx| {
 9320        build_editor_with_project(project.clone(), buffer, window, cx)
 9321    });
 9322    editor.update_in(cx, |editor, window, cx| {
 9323        editor.set_text("one\ntwo\nthree\n", window, cx)
 9324    });
 9325    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9326
 9327    cx.executor().start_waiting();
 9328    let fake_server = fake_servers.next().await.unwrap();
 9329
 9330    {
 9331        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9332            move |params, _| async move {
 9333                assert_eq!(
 9334                    params.text_document.uri,
 9335                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9336                );
 9337                assert_eq!(params.options.tab_size, 4);
 9338                Ok(Some(vec![lsp::TextEdit::new(
 9339                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9340                    ", ".to_string(),
 9341                )]))
 9342            },
 9343        );
 9344        let save = editor
 9345            .update_in(cx, |editor, window, cx| {
 9346                editor.save(
 9347                    SaveOptions {
 9348                        format: true,
 9349                        autosave: false,
 9350                    },
 9351                    project.clone(),
 9352                    window,
 9353                    cx,
 9354                )
 9355            })
 9356            .unwrap();
 9357        cx.executor().start_waiting();
 9358        save.await;
 9359
 9360        assert_eq!(
 9361            editor.update(cx, |editor, cx| editor.text(cx)),
 9362            "one, two\nthree\n"
 9363        );
 9364        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9365    }
 9366
 9367    {
 9368        editor.update_in(cx, |editor, window, cx| {
 9369            editor.set_text("one\ntwo\nthree\n", window, cx)
 9370        });
 9371        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9372
 9373        // Ensure we can still save even if formatting hangs.
 9374        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9375            move |params, _| async move {
 9376                assert_eq!(
 9377                    params.text_document.uri,
 9378                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9379                );
 9380                futures::future::pending::<()>().await;
 9381                unreachable!()
 9382            },
 9383        );
 9384        let save = editor
 9385            .update_in(cx, |editor, window, cx| {
 9386                editor.save(
 9387                    SaveOptions {
 9388                        format: true,
 9389                        autosave: false,
 9390                    },
 9391                    project.clone(),
 9392                    window,
 9393                    cx,
 9394                )
 9395            })
 9396            .unwrap();
 9397        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9398        cx.executor().start_waiting();
 9399        save.await;
 9400        assert_eq!(
 9401            editor.update(cx, |editor, cx| editor.text(cx)),
 9402            "one\ntwo\nthree\n"
 9403        );
 9404    }
 9405
 9406    // Set rust language override and assert overridden tabsize is sent to language server
 9407    update_test_language_settings(cx, |settings| {
 9408        settings.languages.0.insert(
 9409            "Rust".into(),
 9410            LanguageSettingsContent {
 9411                tab_size: NonZeroU32::new(8),
 9412                ..Default::default()
 9413            },
 9414        );
 9415    });
 9416
 9417    {
 9418        editor.update_in(cx, |editor, window, cx| {
 9419            editor.set_text("somehting_new\n", window, cx)
 9420        });
 9421        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9422        let _formatting_request_signal = fake_server
 9423            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9424                assert_eq!(
 9425                    params.text_document.uri,
 9426                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9427                );
 9428                assert_eq!(params.options.tab_size, 8);
 9429                Ok(Some(vec![]))
 9430            });
 9431        let save = editor
 9432            .update_in(cx, |editor, window, cx| {
 9433                editor.save(
 9434                    SaveOptions {
 9435                        format: true,
 9436                        autosave: false,
 9437                    },
 9438                    project.clone(),
 9439                    window,
 9440                    cx,
 9441                )
 9442            })
 9443            .unwrap();
 9444        cx.executor().start_waiting();
 9445        save.await;
 9446    }
 9447}
 9448
 9449#[gpui::test]
 9450async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9451    init_test(cx, |_| {});
 9452
 9453    let cols = 4;
 9454    let rows = 10;
 9455    let sample_text_1 = sample_text(rows, cols, 'a');
 9456    assert_eq!(
 9457        sample_text_1,
 9458        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9459    );
 9460    let sample_text_2 = sample_text(rows, cols, 'l');
 9461    assert_eq!(
 9462        sample_text_2,
 9463        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9464    );
 9465    let sample_text_3 = sample_text(rows, cols, 'v');
 9466    assert_eq!(
 9467        sample_text_3,
 9468        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9469    );
 9470
 9471    let fs = FakeFs::new(cx.executor());
 9472    fs.insert_tree(
 9473        path!("/a"),
 9474        json!({
 9475            "main.rs": sample_text_1,
 9476            "other.rs": sample_text_2,
 9477            "lib.rs": sample_text_3,
 9478        }),
 9479    )
 9480    .await;
 9481
 9482    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9483    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9484    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9485
 9486    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9487    language_registry.add(rust_lang());
 9488    let mut fake_servers = language_registry.register_fake_lsp(
 9489        "Rust",
 9490        FakeLspAdapter {
 9491            capabilities: lsp::ServerCapabilities {
 9492                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9493                ..Default::default()
 9494            },
 9495            ..Default::default()
 9496        },
 9497    );
 9498
 9499    let worktree = project.update(cx, |project, cx| {
 9500        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9501        assert_eq!(worktrees.len(), 1);
 9502        worktrees.pop().unwrap()
 9503    });
 9504    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9505
 9506    let buffer_1 = project
 9507        .update(cx, |project, cx| {
 9508            project.open_buffer((worktree_id, "main.rs"), cx)
 9509        })
 9510        .await
 9511        .unwrap();
 9512    let buffer_2 = project
 9513        .update(cx, |project, cx| {
 9514            project.open_buffer((worktree_id, "other.rs"), cx)
 9515        })
 9516        .await
 9517        .unwrap();
 9518    let buffer_3 = project
 9519        .update(cx, |project, cx| {
 9520            project.open_buffer((worktree_id, "lib.rs"), cx)
 9521        })
 9522        .await
 9523        .unwrap();
 9524
 9525    let multi_buffer = cx.new(|cx| {
 9526        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9527        multi_buffer.push_excerpts(
 9528            buffer_1.clone(),
 9529            [
 9530                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9531                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9532                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9533            ],
 9534            cx,
 9535        );
 9536        multi_buffer.push_excerpts(
 9537            buffer_2.clone(),
 9538            [
 9539                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9540                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9541                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9542            ],
 9543            cx,
 9544        );
 9545        multi_buffer.push_excerpts(
 9546            buffer_3.clone(),
 9547            [
 9548                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9549                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9550                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9551            ],
 9552            cx,
 9553        );
 9554        multi_buffer
 9555    });
 9556    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9557        Editor::new(
 9558            EditorMode::full(),
 9559            multi_buffer,
 9560            Some(project.clone()),
 9561            window,
 9562            cx,
 9563        )
 9564    });
 9565
 9566    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9567        editor.change_selections(
 9568            SelectionEffects::scroll(Autoscroll::Next),
 9569            window,
 9570            cx,
 9571            |s| s.select_ranges(Some(1..2)),
 9572        );
 9573        editor.insert("|one|two|three|", window, cx);
 9574    });
 9575    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9576    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9577        editor.change_selections(
 9578            SelectionEffects::scroll(Autoscroll::Next),
 9579            window,
 9580            cx,
 9581            |s| s.select_ranges(Some(60..70)),
 9582        );
 9583        editor.insert("|four|five|six|", window, cx);
 9584    });
 9585    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9586
 9587    // First two buffers should be edited, but not the third one.
 9588    assert_eq!(
 9589        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9590        "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}",
 9591    );
 9592    buffer_1.update(cx, |buffer, _| {
 9593        assert!(buffer.is_dirty());
 9594        assert_eq!(
 9595            buffer.text(),
 9596            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9597        )
 9598    });
 9599    buffer_2.update(cx, |buffer, _| {
 9600        assert!(buffer.is_dirty());
 9601        assert_eq!(
 9602            buffer.text(),
 9603            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9604        )
 9605    });
 9606    buffer_3.update(cx, |buffer, _| {
 9607        assert!(!buffer.is_dirty());
 9608        assert_eq!(buffer.text(), sample_text_3,)
 9609    });
 9610    cx.executor().run_until_parked();
 9611
 9612    cx.executor().start_waiting();
 9613    let save = multi_buffer_editor
 9614        .update_in(cx, |editor, window, cx| {
 9615            editor.save(
 9616                SaveOptions {
 9617                    format: true,
 9618                    autosave: false,
 9619                },
 9620                project.clone(),
 9621                window,
 9622                cx,
 9623            )
 9624        })
 9625        .unwrap();
 9626
 9627    let fake_server = fake_servers.next().await.unwrap();
 9628    fake_server
 9629        .server
 9630        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9631            Ok(Some(vec![lsp::TextEdit::new(
 9632                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9633                format!("[{} formatted]", params.text_document.uri),
 9634            )]))
 9635        })
 9636        .detach();
 9637    save.await;
 9638
 9639    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9640    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9641    assert_eq!(
 9642        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9643        uri!(
 9644            "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}"
 9645        ),
 9646    );
 9647    buffer_1.update(cx, |buffer, _| {
 9648        assert!(!buffer.is_dirty());
 9649        assert_eq!(
 9650            buffer.text(),
 9651            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9652        )
 9653    });
 9654    buffer_2.update(cx, |buffer, _| {
 9655        assert!(!buffer.is_dirty());
 9656        assert_eq!(
 9657            buffer.text(),
 9658            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9659        )
 9660    });
 9661    buffer_3.update(cx, |buffer, _| {
 9662        assert!(!buffer.is_dirty());
 9663        assert_eq!(buffer.text(), sample_text_3,)
 9664    });
 9665}
 9666
 9667#[gpui::test]
 9668async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9669    init_test(cx, |_| {});
 9670
 9671    let fs = FakeFs::new(cx.executor());
 9672    fs.insert_tree(
 9673        path!("/dir"),
 9674        json!({
 9675            "file1.rs": "fn main() { println!(\"hello\"); }",
 9676            "file2.rs": "fn test() { println!(\"test\"); }",
 9677            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9678        }),
 9679    )
 9680    .await;
 9681
 9682    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9683    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9684    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9685
 9686    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9687    language_registry.add(rust_lang());
 9688
 9689    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9690    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9691
 9692    // Open three buffers
 9693    let buffer_1 = project
 9694        .update(cx, |project, cx| {
 9695            project.open_buffer((worktree_id, "file1.rs"), cx)
 9696        })
 9697        .await
 9698        .unwrap();
 9699    let buffer_2 = project
 9700        .update(cx, |project, cx| {
 9701            project.open_buffer((worktree_id, "file2.rs"), cx)
 9702        })
 9703        .await
 9704        .unwrap();
 9705    let buffer_3 = project
 9706        .update(cx, |project, cx| {
 9707            project.open_buffer((worktree_id, "file3.rs"), cx)
 9708        })
 9709        .await
 9710        .unwrap();
 9711
 9712    // Create a multi-buffer with all three buffers
 9713    let multi_buffer = cx.new(|cx| {
 9714        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9715        multi_buffer.push_excerpts(
 9716            buffer_1.clone(),
 9717            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9718            cx,
 9719        );
 9720        multi_buffer.push_excerpts(
 9721            buffer_2.clone(),
 9722            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9723            cx,
 9724        );
 9725        multi_buffer.push_excerpts(
 9726            buffer_3.clone(),
 9727            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9728            cx,
 9729        );
 9730        multi_buffer
 9731    });
 9732
 9733    let editor = cx.new_window_entity(|window, cx| {
 9734        Editor::new(
 9735            EditorMode::full(),
 9736            multi_buffer,
 9737            Some(project.clone()),
 9738            window,
 9739            cx,
 9740        )
 9741    });
 9742
 9743    // Edit only the first buffer
 9744    editor.update_in(cx, |editor, window, cx| {
 9745        editor.change_selections(
 9746            SelectionEffects::scroll(Autoscroll::Next),
 9747            window,
 9748            cx,
 9749            |s| s.select_ranges(Some(10..10)),
 9750        );
 9751        editor.insert("// edited", window, cx);
 9752    });
 9753
 9754    // Verify that only buffer 1 is dirty
 9755    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9756    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9757    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9758
 9759    // Get write counts after file creation (files were created with initial content)
 9760    // We expect each file to have been written once during creation
 9761    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9762    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9763    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9764
 9765    // Perform autosave
 9766    let save_task = editor.update_in(cx, |editor, window, cx| {
 9767        editor.save(
 9768            SaveOptions {
 9769                format: true,
 9770                autosave: true,
 9771            },
 9772            project.clone(),
 9773            window,
 9774            cx,
 9775        )
 9776    });
 9777    save_task.await.unwrap();
 9778
 9779    // Only the dirty buffer should have been saved
 9780    assert_eq!(
 9781        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9782        1,
 9783        "Buffer 1 was dirty, so it should have been written once during autosave"
 9784    );
 9785    assert_eq!(
 9786        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9787        0,
 9788        "Buffer 2 was clean, so it should not have been written during autosave"
 9789    );
 9790    assert_eq!(
 9791        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9792        0,
 9793        "Buffer 3 was clean, so it should not have been written during autosave"
 9794    );
 9795
 9796    // Verify buffer states after autosave
 9797    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9798    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9799    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9800
 9801    // Now perform a manual save (format = true)
 9802    let save_task = editor.update_in(cx, |editor, window, cx| {
 9803        editor.save(
 9804            SaveOptions {
 9805                format: true,
 9806                autosave: false,
 9807            },
 9808            project.clone(),
 9809            window,
 9810            cx,
 9811        )
 9812    });
 9813    save_task.await.unwrap();
 9814
 9815    // During manual save, clean buffers don't get written to disk
 9816    // They just get did_save called for language server notifications
 9817    assert_eq!(
 9818        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9819        1,
 9820        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9821    );
 9822    assert_eq!(
 9823        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9824        0,
 9825        "Buffer 2 should not have been written at all"
 9826    );
 9827    assert_eq!(
 9828        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9829        0,
 9830        "Buffer 3 should not have been written at all"
 9831    );
 9832}
 9833
 9834#[gpui::test]
 9835async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9836    init_test(cx, |_| {});
 9837
 9838    let fs = FakeFs::new(cx.executor());
 9839    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9840
 9841    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9842
 9843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9844    language_registry.add(rust_lang());
 9845    let mut fake_servers = language_registry.register_fake_lsp(
 9846        "Rust",
 9847        FakeLspAdapter {
 9848            capabilities: lsp::ServerCapabilities {
 9849                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9850                ..Default::default()
 9851            },
 9852            ..Default::default()
 9853        },
 9854    );
 9855
 9856    let buffer = project
 9857        .update(cx, |project, cx| {
 9858            project.open_local_buffer(path!("/file.rs"), cx)
 9859        })
 9860        .await
 9861        .unwrap();
 9862
 9863    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9864    let (editor, cx) = cx.add_window_view(|window, cx| {
 9865        build_editor_with_project(project.clone(), buffer, window, cx)
 9866    });
 9867    editor.update_in(cx, |editor, window, cx| {
 9868        editor.set_text("one\ntwo\nthree\n", window, cx)
 9869    });
 9870    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9871
 9872    cx.executor().start_waiting();
 9873    let fake_server = fake_servers.next().await.unwrap();
 9874
 9875    let save = editor
 9876        .update_in(cx, |editor, window, cx| {
 9877            editor.save(
 9878                SaveOptions {
 9879                    format: true,
 9880                    autosave: false,
 9881                },
 9882                project.clone(),
 9883                window,
 9884                cx,
 9885            )
 9886        })
 9887        .unwrap();
 9888    fake_server
 9889        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9890            assert_eq!(
 9891                params.text_document.uri,
 9892                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9893            );
 9894            assert_eq!(params.options.tab_size, 4);
 9895            Ok(Some(vec![lsp::TextEdit::new(
 9896                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9897                ", ".to_string(),
 9898            )]))
 9899        })
 9900        .next()
 9901        .await;
 9902    cx.executor().start_waiting();
 9903    save.await;
 9904    assert_eq!(
 9905        editor.update(cx, |editor, cx| editor.text(cx)),
 9906        "one, two\nthree\n"
 9907    );
 9908    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9909
 9910    editor.update_in(cx, |editor, window, cx| {
 9911        editor.set_text("one\ntwo\nthree\n", window, cx)
 9912    });
 9913    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9914
 9915    // Ensure we can still save even if formatting hangs.
 9916    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9917        move |params, _| async move {
 9918            assert_eq!(
 9919                params.text_document.uri,
 9920                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9921            );
 9922            futures::future::pending::<()>().await;
 9923            unreachable!()
 9924        },
 9925    );
 9926    let save = editor
 9927        .update_in(cx, |editor, window, cx| {
 9928            editor.save(
 9929                SaveOptions {
 9930                    format: true,
 9931                    autosave: false,
 9932                },
 9933                project.clone(),
 9934                window,
 9935                cx,
 9936            )
 9937        })
 9938        .unwrap();
 9939    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9940    cx.executor().start_waiting();
 9941    save.await;
 9942    assert_eq!(
 9943        editor.update(cx, |editor, cx| editor.text(cx)),
 9944        "one\ntwo\nthree\n"
 9945    );
 9946    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9947
 9948    // For non-dirty buffer, no formatting request should be sent
 9949    let save = editor
 9950        .update_in(cx, |editor, window, cx| {
 9951            editor.save(
 9952                SaveOptions {
 9953                    format: false,
 9954                    autosave: false,
 9955                },
 9956                project.clone(),
 9957                window,
 9958                cx,
 9959            )
 9960        })
 9961        .unwrap();
 9962    let _pending_format_request = fake_server
 9963        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9964            panic!("Should not be invoked");
 9965        })
 9966        .next();
 9967    cx.executor().start_waiting();
 9968    save.await;
 9969
 9970    // Set Rust language override and assert overridden tabsize is sent to language server
 9971    update_test_language_settings(cx, |settings| {
 9972        settings.languages.0.insert(
 9973            "Rust".into(),
 9974            LanguageSettingsContent {
 9975                tab_size: NonZeroU32::new(8),
 9976                ..Default::default()
 9977            },
 9978        );
 9979    });
 9980
 9981    editor.update_in(cx, |editor, window, cx| {
 9982        editor.set_text("somehting_new\n", window, cx)
 9983    });
 9984    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9985    let save = editor
 9986        .update_in(cx, |editor, window, cx| {
 9987            editor.save(
 9988                SaveOptions {
 9989                    format: true,
 9990                    autosave: false,
 9991                },
 9992                project.clone(),
 9993                window,
 9994                cx,
 9995            )
 9996        })
 9997        .unwrap();
 9998    fake_server
 9999        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10000            assert_eq!(
10001                params.text_document.uri,
10002                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10003            );
10004            assert_eq!(params.options.tab_size, 8);
10005            Ok(Some(Vec::new()))
10006        })
10007        .next()
10008        .await;
10009    save.await;
10010}
10011
10012#[gpui::test]
10013async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10014    init_test(cx, |settings| {
10015        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
10016            Formatter::LanguageServer { name: None },
10017        ]))
10018    });
10019
10020    let fs = FakeFs::new(cx.executor());
10021    fs.insert_file(path!("/file.rs"), Default::default()).await;
10022
10023    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10024
10025    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10026    language_registry.add(Arc::new(Language::new(
10027        LanguageConfig {
10028            name: "Rust".into(),
10029            matcher: LanguageMatcher {
10030                path_suffixes: vec!["rs".to_string()],
10031                ..Default::default()
10032            },
10033            ..LanguageConfig::default()
10034        },
10035        Some(tree_sitter_rust::LANGUAGE.into()),
10036    )));
10037    update_test_language_settings(cx, |settings| {
10038        // Enable Prettier formatting for the same buffer, and ensure
10039        // LSP is called instead of Prettier.
10040        settings.defaults.prettier = Some(PrettierSettings {
10041            allowed: true,
10042            ..PrettierSettings::default()
10043        });
10044    });
10045    let mut fake_servers = language_registry.register_fake_lsp(
10046        "Rust",
10047        FakeLspAdapter {
10048            capabilities: lsp::ServerCapabilities {
10049                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10050                ..Default::default()
10051            },
10052            ..Default::default()
10053        },
10054    );
10055
10056    let buffer = project
10057        .update(cx, |project, cx| {
10058            project.open_local_buffer(path!("/file.rs"), cx)
10059        })
10060        .await
10061        .unwrap();
10062
10063    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10064    let (editor, cx) = cx.add_window_view(|window, cx| {
10065        build_editor_with_project(project.clone(), buffer, window, cx)
10066    });
10067    editor.update_in(cx, |editor, window, cx| {
10068        editor.set_text("one\ntwo\nthree\n", window, cx)
10069    });
10070
10071    cx.executor().start_waiting();
10072    let fake_server = fake_servers.next().await.unwrap();
10073
10074    let format = editor
10075        .update_in(cx, |editor, window, cx| {
10076            editor.perform_format(
10077                project.clone(),
10078                FormatTrigger::Manual,
10079                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10080                window,
10081                cx,
10082            )
10083        })
10084        .unwrap();
10085    fake_server
10086        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10087            assert_eq!(
10088                params.text_document.uri,
10089                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10090            );
10091            assert_eq!(params.options.tab_size, 4);
10092            Ok(Some(vec![lsp::TextEdit::new(
10093                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10094                ", ".to_string(),
10095            )]))
10096        })
10097        .next()
10098        .await;
10099    cx.executor().start_waiting();
10100    format.await;
10101    assert_eq!(
10102        editor.update(cx, |editor, cx| editor.text(cx)),
10103        "one, two\nthree\n"
10104    );
10105
10106    editor.update_in(cx, |editor, window, cx| {
10107        editor.set_text("one\ntwo\nthree\n", window, cx)
10108    });
10109    // Ensure we don't lock if formatting hangs.
10110    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10111        move |params, _| async move {
10112            assert_eq!(
10113                params.text_document.uri,
10114                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10115            );
10116            futures::future::pending::<()>().await;
10117            unreachable!()
10118        },
10119    );
10120    let format = editor
10121        .update_in(cx, |editor, window, cx| {
10122            editor.perform_format(
10123                project,
10124                FormatTrigger::Manual,
10125                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10126                window,
10127                cx,
10128            )
10129        })
10130        .unwrap();
10131    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10132    cx.executor().start_waiting();
10133    format.await;
10134    assert_eq!(
10135        editor.update(cx, |editor, cx| editor.text(cx)),
10136        "one\ntwo\nthree\n"
10137    );
10138}
10139
10140#[gpui::test]
10141async fn test_multiple_formatters(cx: &mut TestAppContext) {
10142    init_test(cx, |settings| {
10143        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10144        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
10145            Formatter::LanguageServer { name: None },
10146            Formatter::CodeActions(
10147                [
10148                    ("code-action-1".into(), true),
10149                    ("code-action-2".into(), true),
10150                ]
10151                .into_iter()
10152                .collect(),
10153            ),
10154        ]))
10155    });
10156
10157    let fs = FakeFs::new(cx.executor());
10158    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10159        .await;
10160
10161    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10162    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10163    language_registry.add(rust_lang());
10164
10165    let mut fake_servers = language_registry.register_fake_lsp(
10166        "Rust",
10167        FakeLspAdapter {
10168            capabilities: lsp::ServerCapabilities {
10169                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10170                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10171                    commands: vec!["the-command-for-code-action-1".into()],
10172                    ..Default::default()
10173                }),
10174                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10175                ..Default::default()
10176            },
10177            ..Default::default()
10178        },
10179    );
10180
10181    let buffer = project
10182        .update(cx, |project, cx| {
10183            project.open_local_buffer(path!("/file.rs"), cx)
10184        })
10185        .await
10186        .unwrap();
10187
10188    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10189    let (editor, cx) = cx.add_window_view(|window, cx| {
10190        build_editor_with_project(project.clone(), buffer, window, cx)
10191    });
10192
10193    cx.executor().start_waiting();
10194
10195    let fake_server = fake_servers.next().await.unwrap();
10196    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10197        move |_params, _| async move {
10198            Ok(Some(vec![lsp::TextEdit::new(
10199                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10200                "applied-formatting\n".to_string(),
10201            )]))
10202        },
10203    );
10204    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10205        move |params, _| async move {
10206            assert_eq!(
10207                params.context.only,
10208                Some(vec!["code-action-1".into(), "code-action-2".into()])
10209            );
10210            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10211            Ok(Some(vec![
10212                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10213                    kind: Some("code-action-1".into()),
10214                    edit: Some(lsp::WorkspaceEdit::new(
10215                        [(
10216                            uri.clone(),
10217                            vec![lsp::TextEdit::new(
10218                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10219                                "applied-code-action-1-edit\n".to_string(),
10220                            )],
10221                        )]
10222                        .into_iter()
10223                        .collect(),
10224                    )),
10225                    command: Some(lsp::Command {
10226                        command: "the-command-for-code-action-1".into(),
10227                        ..Default::default()
10228                    }),
10229                    ..Default::default()
10230                }),
10231                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10232                    kind: Some("code-action-2".into()),
10233                    edit: Some(lsp::WorkspaceEdit::new(
10234                        [(
10235                            uri.clone(),
10236                            vec![lsp::TextEdit::new(
10237                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10238                                "applied-code-action-2-edit\n".to_string(),
10239                            )],
10240                        )]
10241                        .into_iter()
10242                        .collect(),
10243                    )),
10244                    ..Default::default()
10245                }),
10246            ]))
10247        },
10248    );
10249
10250    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10251        move |params, _| async move { Ok(params) }
10252    });
10253
10254    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10255    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10256        let fake = fake_server.clone();
10257        let lock = command_lock.clone();
10258        move |params, _| {
10259            assert_eq!(params.command, "the-command-for-code-action-1");
10260            let fake = fake.clone();
10261            let lock = lock.clone();
10262            async move {
10263                lock.lock().await;
10264                fake.server
10265                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10266                        label: None,
10267                        edit: lsp::WorkspaceEdit {
10268                            changes: Some(
10269                                [(
10270                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10271                                    vec![lsp::TextEdit {
10272                                        range: lsp::Range::new(
10273                                            lsp::Position::new(0, 0),
10274                                            lsp::Position::new(0, 0),
10275                                        ),
10276                                        new_text: "applied-code-action-1-command\n".into(),
10277                                    }],
10278                                )]
10279                                .into_iter()
10280                                .collect(),
10281                            ),
10282                            ..Default::default()
10283                        },
10284                    })
10285                    .await
10286                    .into_response()
10287                    .unwrap();
10288                Ok(Some(json!(null)))
10289            }
10290        }
10291    });
10292
10293    cx.executor().start_waiting();
10294    editor
10295        .update_in(cx, |editor, window, cx| {
10296            editor.perform_format(
10297                project.clone(),
10298                FormatTrigger::Manual,
10299                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10300                window,
10301                cx,
10302            )
10303        })
10304        .unwrap()
10305        .await;
10306    editor.update(cx, |editor, cx| {
10307        assert_eq!(
10308            editor.text(cx),
10309            r#"
10310                applied-code-action-2-edit
10311                applied-code-action-1-command
10312                applied-code-action-1-edit
10313                applied-formatting
10314                one
10315                two
10316                three
10317            "#
10318            .unindent()
10319        );
10320    });
10321
10322    editor.update_in(cx, |editor, window, cx| {
10323        editor.undo(&Default::default(), window, cx);
10324        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10325    });
10326
10327    // Perform a manual edit while waiting for an LSP command
10328    // that's being run as part of a formatting code action.
10329    let lock_guard = command_lock.lock().await;
10330    let format = editor
10331        .update_in(cx, |editor, window, cx| {
10332            editor.perform_format(
10333                project.clone(),
10334                FormatTrigger::Manual,
10335                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10336                window,
10337                cx,
10338            )
10339        })
10340        .unwrap();
10341    cx.run_until_parked();
10342    editor.update(cx, |editor, cx| {
10343        assert_eq!(
10344            editor.text(cx),
10345            r#"
10346                applied-code-action-1-edit
10347                applied-formatting
10348                one
10349                two
10350                three
10351            "#
10352            .unindent()
10353        );
10354
10355        editor.buffer.update(cx, |buffer, cx| {
10356            let ix = buffer.len(cx);
10357            buffer.edit([(ix..ix, "edited\n")], None, cx);
10358        });
10359    });
10360
10361    // Allow the LSP command to proceed. Because the buffer was edited,
10362    // the second code action will not be run.
10363    drop(lock_guard);
10364    format.await;
10365    editor.update_in(cx, |editor, window, cx| {
10366        assert_eq!(
10367            editor.text(cx),
10368            r#"
10369                applied-code-action-1-command
10370                applied-code-action-1-edit
10371                applied-formatting
10372                one
10373                two
10374                three
10375                edited
10376            "#
10377            .unindent()
10378        );
10379
10380        // The manual edit is undone first, because it is the last thing the user did
10381        // (even though the command completed afterwards).
10382        editor.undo(&Default::default(), window, cx);
10383        assert_eq!(
10384            editor.text(cx),
10385            r#"
10386                applied-code-action-1-command
10387                applied-code-action-1-edit
10388                applied-formatting
10389                one
10390                two
10391                three
10392            "#
10393            .unindent()
10394        );
10395
10396        // All the formatting (including the command, which completed after the manual edit)
10397        // is undone together.
10398        editor.undo(&Default::default(), window, cx);
10399        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10400    });
10401}
10402
10403#[gpui::test]
10404async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10405    init_test(cx, |settings| {
10406        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
10407            Formatter::LanguageServer { name: None },
10408        ]))
10409    });
10410
10411    let fs = FakeFs::new(cx.executor());
10412    fs.insert_file(path!("/file.ts"), Default::default()).await;
10413
10414    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10415
10416    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10417    language_registry.add(Arc::new(Language::new(
10418        LanguageConfig {
10419            name: "TypeScript".into(),
10420            matcher: LanguageMatcher {
10421                path_suffixes: vec!["ts".to_string()],
10422                ..Default::default()
10423            },
10424            ..LanguageConfig::default()
10425        },
10426        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10427    )));
10428    update_test_language_settings(cx, |settings| {
10429        settings.defaults.prettier = Some(PrettierSettings {
10430            allowed: true,
10431            ..PrettierSettings::default()
10432        });
10433    });
10434    let mut fake_servers = language_registry.register_fake_lsp(
10435        "TypeScript",
10436        FakeLspAdapter {
10437            capabilities: lsp::ServerCapabilities {
10438                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10439                ..Default::default()
10440            },
10441            ..Default::default()
10442        },
10443    );
10444
10445    let buffer = project
10446        .update(cx, |project, cx| {
10447            project.open_local_buffer(path!("/file.ts"), cx)
10448        })
10449        .await
10450        .unwrap();
10451
10452    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10453    let (editor, cx) = cx.add_window_view(|window, cx| {
10454        build_editor_with_project(project.clone(), buffer, window, cx)
10455    });
10456    editor.update_in(cx, |editor, window, cx| {
10457        editor.set_text(
10458            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10459            window,
10460            cx,
10461        )
10462    });
10463
10464    cx.executor().start_waiting();
10465    let fake_server = fake_servers.next().await.unwrap();
10466
10467    let format = editor
10468        .update_in(cx, |editor, window, cx| {
10469            editor.perform_code_action_kind(
10470                project.clone(),
10471                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10472                window,
10473                cx,
10474            )
10475        })
10476        .unwrap();
10477    fake_server
10478        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10479            assert_eq!(
10480                params.text_document.uri,
10481                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10482            );
10483            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10484                lsp::CodeAction {
10485                    title: "Organize Imports".to_string(),
10486                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10487                    edit: Some(lsp::WorkspaceEdit {
10488                        changes: Some(
10489                            [(
10490                                params.text_document.uri.clone(),
10491                                vec![lsp::TextEdit::new(
10492                                    lsp::Range::new(
10493                                        lsp::Position::new(1, 0),
10494                                        lsp::Position::new(2, 0),
10495                                    ),
10496                                    "".to_string(),
10497                                )],
10498                            )]
10499                            .into_iter()
10500                            .collect(),
10501                        ),
10502                        ..Default::default()
10503                    }),
10504                    ..Default::default()
10505                },
10506            )]))
10507        })
10508        .next()
10509        .await;
10510    cx.executor().start_waiting();
10511    format.await;
10512    assert_eq!(
10513        editor.update(cx, |editor, cx| editor.text(cx)),
10514        "import { a } from 'module';\n\nconst x = a;\n"
10515    );
10516
10517    editor.update_in(cx, |editor, window, cx| {
10518        editor.set_text(
10519            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10520            window,
10521            cx,
10522        )
10523    });
10524    // Ensure we don't lock if code action hangs.
10525    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10526        move |params, _| async move {
10527            assert_eq!(
10528                params.text_document.uri,
10529                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10530            );
10531            futures::future::pending::<()>().await;
10532            unreachable!()
10533        },
10534    );
10535    let format = editor
10536        .update_in(cx, |editor, window, cx| {
10537            editor.perform_code_action_kind(
10538                project,
10539                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10540                window,
10541                cx,
10542            )
10543        })
10544        .unwrap();
10545    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10546    cx.executor().start_waiting();
10547    format.await;
10548    assert_eq!(
10549        editor.update(cx, |editor, cx| editor.text(cx)),
10550        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10551    );
10552}
10553
10554#[gpui::test]
10555async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10556    init_test(cx, |_| {});
10557
10558    let mut cx = EditorLspTestContext::new_rust(
10559        lsp::ServerCapabilities {
10560            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10561            ..Default::default()
10562        },
10563        cx,
10564    )
10565    .await;
10566
10567    cx.set_state(indoc! {"
10568        one.twoˇ
10569    "});
10570
10571    // The format request takes a long time. When it completes, it inserts
10572    // a newline and an indent before the `.`
10573    cx.lsp
10574        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10575            let executor = cx.background_executor().clone();
10576            async move {
10577                executor.timer(Duration::from_millis(100)).await;
10578                Ok(Some(vec![lsp::TextEdit {
10579                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10580                    new_text: "\n    ".into(),
10581                }]))
10582            }
10583        });
10584
10585    // Submit a format request.
10586    let format_1 = cx
10587        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10588        .unwrap();
10589    cx.executor().run_until_parked();
10590
10591    // Submit a second format request.
10592    let format_2 = cx
10593        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10594        .unwrap();
10595    cx.executor().run_until_parked();
10596
10597    // Wait for both format requests to complete
10598    cx.executor().advance_clock(Duration::from_millis(200));
10599    cx.executor().start_waiting();
10600    format_1.await.unwrap();
10601    cx.executor().start_waiting();
10602    format_2.await.unwrap();
10603
10604    // The formatting edits only happens once.
10605    cx.assert_editor_state(indoc! {"
10606        one
10607            .twoˇ
10608    "});
10609}
10610
10611#[gpui::test]
10612async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10613    init_test(cx, |settings| {
10614        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10615    });
10616
10617    let mut cx = EditorLspTestContext::new_rust(
10618        lsp::ServerCapabilities {
10619            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10620            ..Default::default()
10621        },
10622        cx,
10623    )
10624    .await;
10625
10626    // Set up a buffer white some trailing whitespace and no trailing newline.
10627    cx.set_state(
10628        &[
10629            "one ",   //
10630            "twoˇ",   //
10631            "three ", //
10632            "four",   //
10633        ]
10634        .join("\n"),
10635    );
10636
10637    // Submit a format request.
10638    let format = cx
10639        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10640        .unwrap();
10641
10642    // Record which buffer changes have been sent to the language server
10643    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10644    cx.lsp
10645        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10646            let buffer_changes = buffer_changes.clone();
10647            move |params, _| {
10648                buffer_changes.lock().extend(
10649                    params
10650                        .content_changes
10651                        .into_iter()
10652                        .map(|e| (e.range.unwrap(), e.text)),
10653                );
10654            }
10655        });
10656
10657    // Handle formatting requests to the language server.
10658    cx.lsp
10659        .set_request_handler::<lsp::request::Formatting, _, _>({
10660            let buffer_changes = buffer_changes.clone();
10661            move |_, _| {
10662                // When formatting is requested, trailing whitespace has already been stripped,
10663                // and the trailing newline has already been added.
10664                assert_eq!(
10665                    &buffer_changes.lock()[1..],
10666                    &[
10667                        (
10668                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10669                            "".into()
10670                        ),
10671                        (
10672                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10673                            "".into()
10674                        ),
10675                        (
10676                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10677                            "\n".into()
10678                        ),
10679                    ]
10680                );
10681
10682                // Insert blank lines between each line of the buffer.
10683                async move {
10684                    Ok(Some(vec![
10685                        lsp::TextEdit {
10686                            range: lsp::Range::new(
10687                                lsp::Position::new(1, 0),
10688                                lsp::Position::new(1, 0),
10689                            ),
10690                            new_text: "\n".into(),
10691                        },
10692                        lsp::TextEdit {
10693                            range: lsp::Range::new(
10694                                lsp::Position::new(2, 0),
10695                                lsp::Position::new(2, 0),
10696                            ),
10697                            new_text: "\n".into(),
10698                        },
10699                    ]))
10700                }
10701            }
10702        });
10703
10704    // After formatting the buffer, the trailing whitespace is stripped,
10705    // a newline is appended, and the edits provided by the language server
10706    // have been applied.
10707    format.await.unwrap();
10708    cx.assert_editor_state(
10709        &[
10710            "one",   //
10711            "",      //
10712            "twoˇ",  //
10713            "",      //
10714            "three", //
10715            "four",  //
10716            "",      //
10717        ]
10718        .join("\n"),
10719    );
10720
10721    // Undoing the formatting undoes the trailing whitespace removal, the
10722    // trailing newline, and the LSP edits.
10723    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10724    cx.assert_editor_state(
10725        &[
10726            "one ",   //
10727            "twoˇ",   //
10728            "three ", //
10729            "four",   //
10730        ]
10731        .join("\n"),
10732    );
10733}
10734
10735#[gpui::test]
10736async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10737    cx: &mut TestAppContext,
10738) {
10739    init_test(cx, |_| {});
10740
10741    cx.update(|cx| {
10742        cx.update_global::<SettingsStore, _>(|settings, cx| {
10743            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10744                settings.auto_signature_help = Some(true);
10745            });
10746        });
10747    });
10748
10749    let mut cx = EditorLspTestContext::new_rust(
10750        lsp::ServerCapabilities {
10751            signature_help_provider: Some(lsp::SignatureHelpOptions {
10752                ..Default::default()
10753            }),
10754            ..Default::default()
10755        },
10756        cx,
10757    )
10758    .await;
10759
10760    let language = Language::new(
10761        LanguageConfig {
10762            name: "Rust".into(),
10763            brackets: BracketPairConfig {
10764                pairs: vec![
10765                    BracketPair {
10766                        start: "{".to_string(),
10767                        end: "}".to_string(),
10768                        close: true,
10769                        surround: true,
10770                        newline: true,
10771                    },
10772                    BracketPair {
10773                        start: "(".to_string(),
10774                        end: ")".to_string(),
10775                        close: true,
10776                        surround: true,
10777                        newline: true,
10778                    },
10779                    BracketPair {
10780                        start: "/*".to_string(),
10781                        end: " */".to_string(),
10782                        close: true,
10783                        surround: true,
10784                        newline: true,
10785                    },
10786                    BracketPair {
10787                        start: "[".to_string(),
10788                        end: "]".to_string(),
10789                        close: false,
10790                        surround: false,
10791                        newline: true,
10792                    },
10793                    BracketPair {
10794                        start: "\"".to_string(),
10795                        end: "\"".to_string(),
10796                        close: true,
10797                        surround: true,
10798                        newline: false,
10799                    },
10800                    BracketPair {
10801                        start: "<".to_string(),
10802                        end: ">".to_string(),
10803                        close: false,
10804                        surround: true,
10805                        newline: true,
10806                    },
10807                ],
10808                ..Default::default()
10809            },
10810            autoclose_before: "})]".to_string(),
10811            ..Default::default()
10812        },
10813        Some(tree_sitter_rust::LANGUAGE.into()),
10814    );
10815    let language = Arc::new(language);
10816
10817    cx.language_registry().add(language.clone());
10818    cx.update_buffer(|buffer, cx| {
10819        buffer.set_language(Some(language), cx);
10820    });
10821
10822    cx.set_state(
10823        &r#"
10824            fn main() {
10825                sampleˇ
10826            }
10827        "#
10828        .unindent(),
10829    );
10830
10831    cx.update_editor(|editor, window, cx| {
10832        editor.handle_input("(", window, cx);
10833    });
10834    cx.assert_editor_state(
10835        &"
10836            fn main() {
10837                sample(ˇ)
10838            }
10839        "
10840        .unindent(),
10841    );
10842
10843    let mocked_response = lsp::SignatureHelp {
10844        signatures: vec![lsp::SignatureInformation {
10845            label: "fn sample(param1: u8, param2: u8)".to_string(),
10846            documentation: None,
10847            parameters: Some(vec![
10848                lsp::ParameterInformation {
10849                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10850                    documentation: None,
10851                },
10852                lsp::ParameterInformation {
10853                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10854                    documentation: None,
10855                },
10856            ]),
10857            active_parameter: None,
10858        }],
10859        active_signature: Some(0),
10860        active_parameter: Some(0),
10861    };
10862    handle_signature_help_request(&mut cx, mocked_response).await;
10863
10864    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10865        .await;
10866
10867    cx.editor(|editor, _, _| {
10868        let signature_help_state = editor.signature_help_state.popover().cloned();
10869        assert_eq!(
10870            signature_help_state.unwrap().label,
10871            "param1: u8, param2: u8"
10872        );
10873    });
10874}
10875
10876#[gpui::test]
10877async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10878    init_test(cx, |_| {});
10879
10880    cx.update(|cx| {
10881        cx.update_global::<SettingsStore, _>(|settings, cx| {
10882            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10883                settings.auto_signature_help = Some(false);
10884                settings.show_signature_help_after_edits = Some(false);
10885            });
10886        });
10887    });
10888
10889    let mut cx = EditorLspTestContext::new_rust(
10890        lsp::ServerCapabilities {
10891            signature_help_provider: Some(lsp::SignatureHelpOptions {
10892                ..Default::default()
10893            }),
10894            ..Default::default()
10895        },
10896        cx,
10897    )
10898    .await;
10899
10900    let language = Language::new(
10901        LanguageConfig {
10902            name: "Rust".into(),
10903            brackets: BracketPairConfig {
10904                pairs: vec![
10905                    BracketPair {
10906                        start: "{".to_string(),
10907                        end: "}".to_string(),
10908                        close: true,
10909                        surround: true,
10910                        newline: true,
10911                    },
10912                    BracketPair {
10913                        start: "(".to_string(),
10914                        end: ")".to_string(),
10915                        close: true,
10916                        surround: true,
10917                        newline: true,
10918                    },
10919                    BracketPair {
10920                        start: "/*".to_string(),
10921                        end: " */".to_string(),
10922                        close: true,
10923                        surround: true,
10924                        newline: true,
10925                    },
10926                    BracketPair {
10927                        start: "[".to_string(),
10928                        end: "]".to_string(),
10929                        close: false,
10930                        surround: false,
10931                        newline: true,
10932                    },
10933                    BracketPair {
10934                        start: "\"".to_string(),
10935                        end: "\"".to_string(),
10936                        close: true,
10937                        surround: true,
10938                        newline: false,
10939                    },
10940                    BracketPair {
10941                        start: "<".to_string(),
10942                        end: ">".to_string(),
10943                        close: false,
10944                        surround: true,
10945                        newline: true,
10946                    },
10947                ],
10948                ..Default::default()
10949            },
10950            autoclose_before: "})]".to_string(),
10951            ..Default::default()
10952        },
10953        Some(tree_sitter_rust::LANGUAGE.into()),
10954    );
10955    let language = Arc::new(language);
10956
10957    cx.language_registry().add(language.clone());
10958    cx.update_buffer(|buffer, cx| {
10959        buffer.set_language(Some(language), cx);
10960    });
10961
10962    // Ensure that signature_help is not called when no signature help is enabled.
10963    cx.set_state(
10964        &r#"
10965            fn main() {
10966                sampleˇ
10967            }
10968        "#
10969        .unindent(),
10970    );
10971    cx.update_editor(|editor, window, cx| {
10972        editor.handle_input("(", window, cx);
10973    });
10974    cx.assert_editor_state(
10975        &"
10976            fn main() {
10977                sample(ˇ)
10978            }
10979        "
10980        .unindent(),
10981    );
10982    cx.editor(|editor, _, _| {
10983        assert!(editor.signature_help_state.task().is_none());
10984    });
10985
10986    let mocked_response = lsp::SignatureHelp {
10987        signatures: vec![lsp::SignatureInformation {
10988            label: "fn sample(param1: u8, param2: u8)".to_string(),
10989            documentation: None,
10990            parameters: Some(vec![
10991                lsp::ParameterInformation {
10992                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10993                    documentation: None,
10994                },
10995                lsp::ParameterInformation {
10996                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10997                    documentation: None,
10998                },
10999            ]),
11000            active_parameter: None,
11001        }],
11002        active_signature: Some(0),
11003        active_parameter: Some(0),
11004    };
11005
11006    // Ensure that signature_help is called when enabled afte edits
11007    cx.update(|_, cx| {
11008        cx.update_global::<SettingsStore, _>(|settings, cx| {
11009            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11010                settings.auto_signature_help = Some(false);
11011                settings.show_signature_help_after_edits = Some(true);
11012            });
11013        });
11014    });
11015    cx.set_state(
11016        &r#"
11017            fn main() {
11018                sampleˇ
11019            }
11020        "#
11021        .unindent(),
11022    );
11023    cx.update_editor(|editor, window, cx| {
11024        editor.handle_input("(", window, cx);
11025    });
11026    cx.assert_editor_state(
11027        &"
11028            fn main() {
11029                sample(ˇ)
11030            }
11031        "
11032        .unindent(),
11033    );
11034    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11035    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11036        .await;
11037    cx.update_editor(|editor, _, _| {
11038        let signature_help_state = editor.signature_help_state.popover().cloned();
11039        assert!(signature_help_state.is_some());
11040        assert_eq!(
11041            signature_help_state.unwrap().label,
11042            "param1: u8, param2: u8"
11043        );
11044        editor.signature_help_state = SignatureHelpState::default();
11045    });
11046
11047    // Ensure that signature_help is called when auto signature help override is enabled
11048    cx.update(|_, cx| {
11049        cx.update_global::<SettingsStore, _>(|settings, cx| {
11050            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11051                settings.auto_signature_help = Some(true);
11052                settings.show_signature_help_after_edits = Some(false);
11053            });
11054        });
11055    });
11056    cx.set_state(
11057        &r#"
11058            fn main() {
11059                sampleˇ
11060            }
11061        "#
11062        .unindent(),
11063    );
11064    cx.update_editor(|editor, window, cx| {
11065        editor.handle_input("(", window, cx);
11066    });
11067    cx.assert_editor_state(
11068        &"
11069            fn main() {
11070                sample(ˇ)
11071            }
11072        "
11073        .unindent(),
11074    );
11075    handle_signature_help_request(&mut cx, mocked_response).await;
11076    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11077        .await;
11078    cx.editor(|editor, _, _| {
11079        let signature_help_state = editor.signature_help_state.popover().cloned();
11080        assert!(signature_help_state.is_some());
11081        assert_eq!(
11082            signature_help_state.unwrap().label,
11083            "param1: u8, param2: u8"
11084        );
11085    });
11086}
11087
11088#[gpui::test]
11089async fn test_signature_help(cx: &mut TestAppContext) {
11090    init_test(cx, |_| {});
11091    cx.update(|cx| {
11092        cx.update_global::<SettingsStore, _>(|settings, cx| {
11093            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11094                settings.auto_signature_help = Some(true);
11095            });
11096        });
11097    });
11098
11099    let mut cx = EditorLspTestContext::new_rust(
11100        lsp::ServerCapabilities {
11101            signature_help_provider: Some(lsp::SignatureHelpOptions {
11102                ..Default::default()
11103            }),
11104            ..Default::default()
11105        },
11106        cx,
11107    )
11108    .await;
11109
11110    // A test that directly calls `show_signature_help`
11111    cx.update_editor(|editor, window, cx| {
11112        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11113    });
11114
11115    let mocked_response = lsp::SignatureHelp {
11116        signatures: vec![lsp::SignatureInformation {
11117            label: "fn sample(param1: u8, param2: u8)".to_string(),
11118            documentation: None,
11119            parameters: Some(vec![
11120                lsp::ParameterInformation {
11121                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11122                    documentation: None,
11123                },
11124                lsp::ParameterInformation {
11125                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11126                    documentation: None,
11127                },
11128            ]),
11129            active_parameter: None,
11130        }],
11131        active_signature: Some(0),
11132        active_parameter: Some(0),
11133    };
11134    handle_signature_help_request(&mut cx, mocked_response).await;
11135
11136    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11137        .await;
11138
11139    cx.editor(|editor, _, _| {
11140        let signature_help_state = editor.signature_help_state.popover().cloned();
11141        assert!(signature_help_state.is_some());
11142        assert_eq!(
11143            signature_help_state.unwrap().label,
11144            "param1: u8, param2: u8"
11145        );
11146    });
11147
11148    // When exiting outside from inside the brackets, `signature_help` is closed.
11149    cx.set_state(indoc! {"
11150        fn main() {
11151            sample(ˇ);
11152        }
11153
11154        fn sample(param1: u8, param2: u8) {}
11155    "});
11156
11157    cx.update_editor(|editor, window, cx| {
11158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11159            s.select_ranges([0..0])
11160        });
11161    });
11162
11163    let mocked_response = lsp::SignatureHelp {
11164        signatures: Vec::new(),
11165        active_signature: None,
11166        active_parameter: None,
11167    };
11168    handle_signature_help_request(&mut cx, mocked_response).await;
11169
11170    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11171        .await;
11172
11173    cx.editor(|editor, _, _| {
11174        assert!(!editor.signature_help_state.is_shown());
11175    });
11176
11177    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11178    cx.set_state(indoc! {"
11179        fn main() {
11180            sample(ˇ);
11181        }
11182
11183        fn sample(param1: u8, param2: u8) {}
11184    "});
11185
11186    let mocked_response = lsp::SignatureHelp {
11187        signatures: vec![lsp::SignatureInformation {
11188            label: "fn sample(param1: u8, param2: u8)".to_string(),
11189            documentation: None,
11190            parameters: Some(vec![
11191                lsp::ParameterInformation {
11192                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11193                    documentation: None,
11194                },
11195                lsp::ParameterInformation {
11196                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11197                    documentation: None,
11198                },
11199            ]),
11200            active_parameter: None,
11201        }],
11202        active_signature: Some(0),
11203        active_parameter: Some(0),
11204    };
11205    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11206    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11207        .await;
11208    cx.editor(|editor, _, _| {
11209        assert!(editor.signature_help_state.is_shown());
11210    });
11211
11212    // Restore the popover with more parameter input
11213    cx.set_state(indoc! {"
11214        fn main() {
11215            sample(param1, param2ˇ);
11216        }
11217
11218        fn sample(param1: u8, param2: u8) {}
11219    "});
11220
11221    let mocked_response = lsp::SignatureHelp {
11222        signatures: vec![lsp::SignatureInformation {
11223            label: "fn sample(param1: u8, param2: u8)".to_string(),
11224            documentation: None,
11225            parameters: Some(vec![
11226                lsp::ParameterInformation {
11227                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11228                    documentation: None,
11229                },
11230                lsp::ParameterInformation {
11231                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11232                    documentation: None,
11233                },
11234            ]),
11235            active_parameter: None,
11236        }],
11237        active_signature: Some(0),
11238        active_parameter: Some(1),
11239    };
11240    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11241    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11242        .await;
11243
11244    // When selecting a range, the popover is gone.
11245    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11246    cx.update_editor(|editor, window, cx| {
11247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11248            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11249        })
11250    });
11251    cx.assert_editor_state(indoc! {"
11252        fn main() {
11253            sample(param1, «ˇparam2»);
11254        }
11255
11256        fn sample(param1: u8, param2: u8) {}
11257    "});
11258    cx.editor(|editor, _, _| {
11259        assert!(!editor.signature_help_state.is_shown());
11260    });
11261
11262    // When unselecting again, the popover is back if within the brackets.
11263    cx.update_editor(|editor, window, cx| {
11264        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11265            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11266        })
11267    });
11268    cx.assert_editor_state(indoc! {"
11269        fn main() {
11270            sample(param1, ˇparam2);
11271        }
11272
11273        fn sample(param1: u8, param2: u8) {}
11274    "});
11275    handle_signature_help_request(&mut cx, mocked_response).await;
11276    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11277        .await;
11278    cx.editor(|editor, _, _| {
11279        assert!(editor.signature_help_state.is_shown());
11280    });
11281
11282    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11283    cx.update_editor(|editor, window, cx| {
11284        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11285            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11286            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11287        })
11288    });
11289    cx.assert_editor_state(indoc! {"
11290        fn main() {
11291            sample(param1, ˇparam2);
11292        }
11293
11294        fn sample(param1: u8, param2: u8) {}
11295    "});
11296
11297    let mocked_response = lsp::SignatureHelp {
11298        signatures: vec![lsp::SignatureInformation {
11299            label: "fn sample(param1: u8, param2: u8)".to_string(),
11300            documentation: None,
11301            parameters: Some(vec![
11302                lsp::ParameterInformation {
11303                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11304                    documentation: None,
11305                },
11306                lsp::ParameterInformation {
11307                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11308                    documentation: None,
11309                },
11310            ]),
11311            active_parameter: None,
11312        }],
11313        active_signature: Some(0),
11314        active_parameter: Some(1),
11315    };
11316    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11317    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11318        .await;
11319    cx.update_editor(|editor, _, cx| {
11320        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11321    });
11322    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11323        .await;
11324    cx.update_editor(|editor, window, cx| {
11325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11326            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11327        })
11328    });
11329    cx.assert_editor_state(indoc! {"
11330        fn main() {
11331            sample(param1, «ˇparam2»);
11332        }
11333
11334        fn sample(param1: u8, param2: u8) {}
11335    "});
11336    cx.update_editor(|editor, window, cx| {
11337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11338            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11339        })
11340    });
11341    cx.assert_editor_state(indoc! {"
11342        fn main() {
11343            sample(param1, ˇparam2);
11344        }
11345
11346        fn sample(param1: u8, param2: u8) {}
11347    "});
11348    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11349        .await;
11350}
11351
11352#[gpui::test]
11353async fn test_completion_mode(cx: &mut TestAppContext) {
11354    init_test(cx, |_| {});
11355    let mut cx = EditorLspTestContext::new_rust(
11356        lsp::ServerCapabilities {
11357            completion_provider: Some(lsp::CompletionOptions {
11358                resolve_provider: Some(true),
11359                ..Default::default()
11360            }),
11361            ..Default::default()
11362        },
11363        cx,
11364    )
11365    .await;
11366
11367    struct Run {
11368        run_description: &'static str,
11369        initial_state: String,
11370        buffer_marked_text: String,
11371        completion_label: &'static str,
11372        completion_text: &'static str,
11373        expected_with_insert_mode: String,
11374        expected_with_replace_mode: String,
11375        expected_with_replace_subsequence_mode: String,
11376        expected_with_replace_suffix_mode: String,
11377    }
11378
11379    let runs = [
11380        Run {
11381            run_description: "Start of word matches completion text",
11382            initial_state: "before ediˇ after".into(),
11383            buffer_marked_text: "before <edi|> after".into(),
11384            completion_label: "editor",
11385            completion_text: "editor",
11386            expected_with_insert_mode: "before editorˇ after".into(),
11387            expected_with_replace_mode: "before editorˇ after".into(),
11388            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11389            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11390        },
11391        Run {
11392            run_description: "Accept same text at the middle of the word",
11393            initial_state: "before ediˇtor after".into(),
11394            buffer_marked_text: "before <edi|tor> after".into(),
11395            completion_label: "editor",
11396            completion_text: "editor",
11397            expected_with_insert_mode: "before editorˇtor after".into(),
11398            expected_with_replace_mode: "before editorˇ after".into(),
11399            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11400            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11401        },
11402        Run {
11403            run_description: "End of word matches completion text -- cursor at end",
11404            initial_state: "before torˇ after".into(),
11405            buffer_marked_text: "before <tor|> after".into(),
11406            completion_label: "editor",
11407            completion_text: "editor",
11408            expected_with_insert_mode: "before editorˇ after".into(),
11409            expected_with_replace_mode: "before editorˇ after".into(),
11410            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11411            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11412        },
11413        Run {
11414            run_description: "End of word matches completion text -- cursor at start",
11415            initial_state: "before ˇtor after".into(),
11416            buffer_marked_text: "before <|tor> after".into(),
11417            completion_label: "editor",
11418            completion_text: "editor",
11419            expected_with_insert_mode: "before editorˇtor after".into(),
11420            expected_with_replace_mode: "before editorˇ after".into(),
11421            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11422            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11423        },
11424        Run {
11425            run_description: "Prepend text containing whitespace",
11426            initial_state: "pˇfield: bool".into(),
11427            buffer_marked_text: "<p|field>: bool".into(),
11428            completion_label: "pub ",
11429            completion_text: "pub ",
11430            expected_with_insert_mode: "pub ˇfield: bool".into(),
11431            expected_with_replace_mode: "pub ˇ: bool".into(),
11432            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11433            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11434        },
11435        Run {
11436            run_description: "Add element to start of list",
11437            initial_state: "[element_ˇelement_2]".into(),
11438            buffer_marked_text: "[<element_|element_2>]".into(),
11439            completion_label: "element_1",
11440            completion_text: "element_1",
11441            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11442            expected_with_replace_mode: "[element_1ˇ]".into(),
11443            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11444            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11445        },
11446        Run {
11447            run_description: "Add element to start of list -- first and second elements are equal",
11448            initial_state: "[elˇelement]".into(),
11449            buffer_marked_text: "[<el|element>]".into(),
11450            completion_label: "element",
11451            completion_text: "element",
11452            expected_with_insert_mode: "[elementˇelement]".into(),
11453            expected_with_replace_mode: "[elementˇ]".into(),
11454            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11455            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11456        },
11457        Run {
11458            run_description: "Ends with matching suffix",
11459            initial_state: "SubˇError".into(),
11460            buffer_marked_text: "<Sub|Error>".into(),
11461            completion_label: "SubscriptionError",
11462            completion_text: "SubscriptionError",
11463            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11464            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11465            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11466            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11467        },
11468        Run {
11469            run_description: "Suffix is a subsequence -- contiguous",
11470            initial_state: "SubˇErr".into(),
11471            buffer_marked_text: "<Sub|Err>".into(),
11472            completion_label: "SubscriptionError",
11473            completion_text: "SubscriptionError",
11474            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11475            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11476            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11477            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11478        },
11479        Run {
11480            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11481            initial_state: "Suˇscrirr".into(),
11482            buffer_marked_text: "<Su|scrirr>".into(),
11483            completion_label: "SubscriptionError",
11484            completion_text: "SubscriptionError",
11485            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11486            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11487            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11488            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11489        },
11490        Run {
11491            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11492            initial_state: "foo(indˇix)".into(),
11493            buffer_marked_text: "foo(<ind|ix>)".into(),
11494            completion_label: "node_index",
11495            completion_text: "node_index",
11496            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11497            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11498            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11499            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11500        },
11501        Run {
11502            run_description: "Replace range ends before cursor - should extend to cursor",
11503            initial_state: "before editˇo after".into(),
11504            buffer_marked_text: "before <{ed}>it|o after".into(),
11505            completion_label: "editor",
11506            completion_text: "editor",
11507            expected_with_insert_mode: "before editorˇo after".into(),
11508            expected_with_replace_mode: "before editorˇo after".into(),
11509            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11510            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11511        },
11512        Run {
11513            run_description: "Uses label for suffix matching",
11514            initial_state: "before ediˇtor after".into(),
11515            buffer_marked_text: "before <edi|tor> after".into(),
11516            completion_label: "editor",
11517            completion_text: "editor()",
11518            expected_with_insert_mode: "before editor()ˇtor after".into(),
11519            expected_with_replace_mode: "before editor()ˇ after".into(),
11520            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11521            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11522        },
11523        Run {
11524            run_description: "Case insensitive subsequence and suffix matching",
11525            initial_state: "before EDiˇtoR after".into(),
11526            buffer_marked_text: "before <EDi|toR> after".into(),
11527            completion_label: "editor",
11528            completion_text: "editor",
11529            expected_with_insert_mode: "before editorˇtoR after".into(),
11530            expected_with_replace_mode: "before editorˇ after".into(),
11531            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11532            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11533        },
11534    ];
11535
11536    for run in runs {
11537        let run_variations = [
11538            (LspInsertMode::Insert, run.expected_with_insert_mode),
11539            (LspInsertMode::Replace, run.expected_with_replace_mode),
11540            (
11541                LspInsertMode::ReplaceSubsequence,
11542                run.expected_with_replace_subsequence_mode,
11543            ),
11544            (
11545                LspInsertMode::ReplaceSuffix,
11546                run.expected_with_replace_suffix_mode,
11547            ),
11548        ];
11549
11550        for (lsp_insert_mode, expected_text) in run_variations {
11551            eprintln!(
11552                "run = {:?}, mode = {lsp_insert_mode:.?}",
11553                run.run_description,
11554            );
11555
11556            update_test_language_settings(&mut cx, |settings| {
11557                settings.defaults.completions = Some(CompletionSettings {
11558                    lsp_insert_mode,
11559                    words: WordsCompletionMode::Disabled,
11560                    lsp: true,
11561                    lsp_fetch_timeout_ms: 0,
11562                });
11563            });
11564
11565            cx.set_state(&run.initial_state);
11566            cx.update_editor(|editor, window, cx| {
11567                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11568            });
11569
11570            let counter = Arc::new(AtomicUsize::new(0));
11571            handle_completion_request_with_insert_and_replace(
11572                &mut cx,
11573                &run.buffer_marked_text,
11574                vec![(run.completion_label, run.completion_text)],
11575                counter.clone(),
11576            )
11577            .await;
11578            cx.condition(|editor, _| editor.context_menu_visible())
11579                .await;
11580            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11581
11582            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11583                editor
11584                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11585                    .unwrap()
11586            });
11587            cx.assert_editor_state(&expected_text);
11588            handle_resolve_completion_request(&mut cx, None).await;
11589            apply_additional_edits.await.unwrap();
11590        }
11591    }
11592}
11593
11594#[gpui::test]
11595async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11596    init_test(cx, |_| {});
11597    let mut cx = EditorLspTestContext::new_rust(
11598        lsp::ServerCapabilities {
11599            completion_provider: Some(lsp::CompletionOptions {
11600                resolve_provider: Some(true),
11601                ..Default::default()
11602            }),
11603            ..Default::default()
11604        },
11605        cx,
11606    )
11607    .await;
11608
11609    let initial_state = "SubˇError";
11610    let buffer_marked_text = "<Sub|Error>";
11611    let completion_text = "SubscriptionError";
11612    let expected_with_insert_mode = "SubscriptionErrorˇError";
11613    let expected_with_replace_mode = "SubscriptionErrorˇ";
11614
11615    update_test_language_settings(&mut cx, |settings| {
11616        settings.defaults.completions = Some(CompletionSettings {
11617            words: WordsCompletionMode::Disabled,
11618            // set the opposite here to ensure that the action is overriding the default behavior
11619            lsp_insert_mode: LspInsertMode::Insert,
11620            lsp: true,
11621            lsp_fetch_timeout_ms: 0,
11622        });
11623    });
11624
11625    cx.set_state(initial_state);
11626    cx.update_editor(|editor, window, cx| {
11627        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11628    });
11629
11630    let counter = Arc::new(AtomicUsize::new(0));
11631    handle_completion_request_with_insert_and_replace(
11632        &mut cx,
11633        &buffer_marked_text,
11634        vec![(completion_text, completion_text)],
11635        counter.clone(),
11636    )
11637    .await;
11638    cx.condition(|editor, _| editor.context_menu_visible())
11639        .await;
11640    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11641
11642    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11643        editor
11644            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11645            .unwrap()
11646    });
11647    cx.assert_editor_state(&expected_with_replace_mode);
11648    handle_resolve_completion_request(&mut cx, None).await;
11649    apply_additional_edits.await.unwrap();
11650
11651    update_test_language_settings(&mut cx, |settings| {
11652        settings.defaults.completions = Some(CompletionSettings {
11653            words: WordsCompletionMode::Disabled,
11654            // set the opposite here to ensure that the action is overriding the default behavior
11655            lsp_insert_mode: LspInsertMode::Replace,
11656            lsp: true,
11657            lsp_fetch_timeout_ms: 0,
11658        });
11659    });
11660
11661    cx.set_state(initial_state);
11662    cx.update_editor(|editor, window, cx| {
11663        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11664    });
11665    handle_completion_request_with_insert_and_replace(
11666        &mut cx,
11667        &buffer_marked_text,
11668        vec![(completion_text, completion_text)],
11669        counter.clone(),
11670    )
11671    .await;
11672    cx.condition(|editor, _| editor.context_menu_visible())
11673        .await;
11674    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11675
11676    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11677        editor
11678            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11679            .unwrap()
11680    });
11681    cx.assert_editor_state(&expected_with_insert_mode);
11682    handle_resolve_completion_request(&mut cx, None).await;
11683    apply_additional_edits.await.unwrap();
11684}
11685
11686#[gpui::test]
11687async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11688    init_test(cx, |_| {});
11689    let mut cx = EditorLspTestContext::new_rust(
11690        lsp::ServerCapabilities {
11691            completion_provider: Some(lsp::CompletionOptions {
11692                resolve_provider: Some(true),
11693                ..Default::default()
11694            }),
11695            ..Default::default()
11696        },
11697        cx,
11698    )
11699    .await;
11700
11701    // scenario: surrounding text matches completion text
11702    let completion_text = "to_offset";
11703    let initial_state = indoc! {"
11704        1. buf.to_offˇsuffix
11705        2. buf.to_offˇsuf
11706        3. buf.to_offˇfix
11707        4. buf.to_offˇ
11708        5. into_offˇensive
11709        6. ˇsuffix
11710        7. let ˇ //
11711        8. aaˇzz
11712        9. buf.to_off«zzzzzˇ»suffix
11713        10. buf.«ˇzzzzz»suffix
11714        11. to_off«ˇzzzzz»
11715
11716        buf.to_offˇsuffix  // newest cursor
11717    "};
11718    let completion_marked_buffer = indoc! {"
11719        1. buf.to_offsuffix
11720        2. buf.to_offsuf
11721        3. buf.to_offfix
11722        4. buf.to_off
11723        5. into_offensive
11724        6. suffix
11725        7. let  //
11726        8. aazz
11727        9. buf.to_offzzzzzsuffix
11728        10. buf.zzzzzsuffix
11729        11. to_offzzzzz
11730
11731        buf.<to_off|suffix>  // newest cursor
11732    "};
11733    let expected = indoc! {"
11734        1. buf.to_offsetˇ
11735        2. buf.to_offsetˇsuf
11736        3. buf.to_offsetˇfix
11737        4. buf.to_offsetˇ
11738        5. into_offsetˇensive
11739        6. to_offsetˇsuffix
11740        7. let to_offsetˇ //
11741        8. aato_offsetˇzz
11742        9. buf.to_offsetˇ
11743        10. buf.to_offsetˇsuffix
11744        11. to_offsetˇ
11745
11746        buf.to_offsetˇ  // newest cursor
11747    "};
11748    cx.set_state(initial_state);
11749    cx.update_editor(|editor, window, cx| {
11750        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11751    });
11752    handle_completion_request_with_insert_and_replace(
11753        &mut cx,
11754        completion_marked_buffer,
11755        vec![(completion_text, completion_text)],
11756        Arc::new(AtomicUsize::new(0)),
11757    )
11758    .await;
11759    cx.condition(|editor, _| editor.context_menu_visible())
11760        .await;
11761    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11762        editor
11763            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11764            .unwrap()
11765    });
11766    cx.assert_editor_state(expected);
11767    handle_resolve_completion_request(&mut cx, None).await;
11768    apply_additional_edits.await.unwrap();
11769
11770    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11771    let completion_text = "foo_and_bar";
11772    let initial_state = indoc! {"
11773        1. ooanbˇ
11774        2. zooanbˇ
11775        3. ooanbˇz
11776        4. zooanbˇz
11777        5. ooanˇ
11778        6. oanbˇ
11779
11780        ooanbˇ
11781    "};
11782    let completion_marked_buffer = indoc! {"
11783        1. ooanb
11784        2. zooanb
11785        3. ooanbz
11786        4. zooanbz
11787        5. ooan
11788        6. oanb
11789
11790        <ooanb|>
11791    "};
11792    let expected = indoc! {"
11793        1. foo_and_barˇ
11794        2. zfoo_and_barˇ
11795        3. foo_and_barˇz
11796        4. zfoo_and_barˇz
11797        5. ooanfoo_and_barˇ
11798        6. oanbfoo_and_barˇ
11799
11800        foo_and_barˇ
11801    "};
11802    cx.set_state(initial_state);
11803    cx.update_editor(|editor, window, cx| {
11804        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11805    });
11806    handle_completion_request_with_insert_and_replace(
11807        &mut cx,
11808        completion_marked_buffer,
11809        vec![(completion_text, completion_text)],
11810        Arc::new(AtomicUsize::new(0)),
11811    )
11812    .await;
11813    cx.condition(|editor, _| editor.context_menu_visible())
11814        .await;
11815    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11816        editor
11817            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11818            .unwrap()
11819    });
11820    cx.assert_editor_state(expected);
11821    handle_resolve_completion_request(&mut cx, None).await;
11822    apply_additional_edits.await.unwrap();
11823
11824    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11825    // (expects the same as if it was inserted at the end)
11826    let completion_text = "foo_and_bar";
11827    let initial_state = indoc! {"
11828        1. ooˇanb
11829        2. zooˇanb
11830        3. ooˇanbz
11831        4. zooˇanbz
11832
11833        ooˇanb
11834    "};
11835    let completion_marked_buffer = indoc! {"
11836        1. ooanb
11837        2. zooanb
11838        3. ooanbz
11839        4. zooanbz
11840
11841        <oo|anb>
11842    "};
11843    let expected = indoc! {"
11844        1. foo_and_barˇ
11845        2. zfoo_and_barˇ
11846        3. foo_and_barˇz
11847        4. zfoo_and_barˇz
11848
11849        foo_and_barˇ
11850    "};
11851    cx.set_state(initial_state);
11852    cx.update_editor(|editor, window, cx| {
11853        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11854    });
11855    handle_completion_request_with_insert_and_replace(
11856        &mut cx,
11857        completion_marked_buffer,
11858        vec![(completion_text, completion_text)],
11859        Arc::new(AtomicUsize::new(0)),
11860    )
11861    .await;
11862    cx.condition(|editor, _| editor.context_menu_visible())
11863        .await;
11864    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11865        editor
11866            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11867            .unwrap()
11868    });
11869    cx.assert_editor_state(expected);
11870    handle_resolve_completion_request(&mut cx, None).await;
11871    apply_additional_edits.await.unwrap();
11872}
11873
11874// This used to crash
11875#[gpui::test]
11876async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11877    init_test(cx, |_| {});
11878
11879    let buffer_text = indoc! {"
11880        fn main() {
11881            10.satu;
11882
11883            //
11884            // separate cursors so they open in different excerpts (manually reproducible)
11885            //
11886
11887            10.satu20;
11888        }
11889    "};
11890    let multibuffer_text_with_selections = indoc! {"
11891        fn main() {
11892            10.satuˇ;
11893
11894            //
11895
11896            //
11897
11898            10.satuˇ20;
11899        }
11900    "};
11901    let expected_multibuffer = indoc! {"
11902        fn main() {
11903            10.saturating_sub()ˇ;
11904
11905            //
11906
11907            //
11908
11909            10.saturating_sub()ˇ;
11910        }
11911    "};
11912
11913    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11914    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11915
11916    let fs = FakeFs::new(cx.executor());
11917    fs.insert_tree(
11918        path!("/a"),
11919        json!({
11920            "main.rs": buffer_text,
11921        }),
11922    )
11923    .await;
11924
11925    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11926    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11927    language_registry.add(rust_lang());
11928    let mut fake_servers = language_registry.register_fake_lsp(
11929        "Rust",
11930        FakeLspAdapter {
11931            capabilities: lsp::ServerCapabilities {
11932                completion_provider: Some(lsp::CompletionOptions {
11933                    resolve_provider: None,
11934                    ..lsp::CompletionOptions::default()
11935                }),
11936                ..lsp::ServerCapabilities::default()
11937            },
11938            ..FakeLspAdapter::default()
11939        },
11940    );
11941    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11942    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11943    let buffer = project
11944        .update(cx, |project, cx| {
11945            project.open_local_buffer(path!("/a/main.rs"), cx)
11946        })
11947        .await
11948        .unwrap();
11949
11950    let multi_buffer = cx.new(|cx| {
11951        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11952        multi_buffer.push_excerpts(
11953            buffer.clone(),
11954            [ExcerptRange::new(0..first_excerpt_end)],
11955            cx,
11956        );
11957        multi_buffer.push_excerpts(
11958            buffer.clone(),
11959            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11960            cx,
11961        );
11962        multi_buffer
11963    });
11964
11965    let editor = workspace
11966        .update(cx, |_, window, cx| {
11967            cx.new(|cx| {
11968                Editor::new(
11969                    EditorMode::Full {
11970                        scale_ui_elements_with_buffer_font_size: false,
11971                        show_active_line_background: false,
11972                        sized_by_content: false,
11973                    },
11974                    multi_buffer.clone(),
11975                    Some(project.clone()),
11976                    window,
11977                    cx,
11978                )
11979            })
11980        })
11981        .unwrap();
11982
11983    let pane = workspace
11984        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11985        .unwrap();
11986    pane.update_in(cx, |pane, window, cx| {
11987        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11988    });
11989
11990    let fake_server = fake_servers.next().await.unwrap();
11991
11992    editor.update_in(cx, |editor, window, cx| {
11993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11994            s.select_ranges([
11995                Point::new(1, 11)..Point::new(1, 11),
11996                Point::new(7, 11)..Point::new(7, 11),
11997            ])
11998        });
11999
12000        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12001    });
12002
12003    editor.update_in(cx, |editor, window, cx| {
12004        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12005    });
12006
12007    fake_server
12008        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12009            let completion_item = lsp::CompletionItem {
12010                label: "saturating_sub()".into(),
12011                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12012                    lsp::InsertReplaceEdit {
12013                        new_text: "saturating_sub()".to_owned(),
12014                        insert: lsp::Range::new(
12015                            lsp::Position::new(7, 7),
12016                            lsp::Position::new(7, 11),
12017                        ),
12018                        replace: lsp::Range::new(
12019                            lsp::Position::new(7, 7),
12020                            lsp::Position::new(7, 13),
12021                        ),
12022                    },
12023                )),
12024                ..lsp::CompletionItem::default()
12025            };
12026
12027            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12028        })
12029        .next()
12030        .await
12031        .unwrap();
12032
12033    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12034        .await;
12035
12036    editor
12037        .update_in(cx, |editor, window, cx| {
12038            editor
12039                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12040                .unwrap()
12041        })
12042        .await
12043        .unwrap();
12044
12045    editor.update(cx, |editor, cx| {
12046        assert_text_with_selections(editor, expected_multibuffer, cx);
12047    })
12048}
12049
12050#[gpui::test]
12051async fn test_completion(cx: &mut TestAppContext) {
12052    init_test(cx, |_| {});
12053
12054    let mut cx = EditorLspTestContext::new_rust(
12055        lsp::ServerCapabilities {
12056            completion_provider: Some(lsp::CompletionOptions {
12057                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12058                resolve_provider: Some(true),
12059                ..Default::default()
12060            }),
12061            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12062            ..Default::default()
12063        },
12064        cx,
12065    )
12066    .await;
12067    let counter = Arc::new(AtomicUsize::new(0));
12068
12069    cx.set_state(indoc! {"
12070        oneˇ
12071        two
12072        three
12073    "});
12074    cx.simulate_keystroke(".");
12075    handle_completion_request(
12076        indoc! {"
12077            one.|<>
12078            two
12079            three
12080        "},
12081        vec!["first_completion", "second_completion"],
12082        true,
12083        counter.clone(),
12084        &mut cx,
12085    )
12086    .await;
12087    cx.condition(|editor, _| editor.context_menu_visible())
12088        .await;
12089    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12090
12091    let _handler = handle_signature_help_request(
12092        &mut cx,
12093        lsp::SignatureHelp {
12094            signatures: vec![lsp::SignatureInformation {
12095                label: "test signature".to_string(),
12096                documentation: None,
12097                parameters: Some(vec![lsp::ParameterInformation {
12098                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12099                    documentation: None,
12100                }]),
12101                active_parameter: None,
12102            }],
12103            active_signature: None,
12104            active_parameter: None,
12105        },
12106    );
12107    cx.update_editor(|editor, window, cx| {
12108        assert!(
12109            !editor.signature_help_state.is_shown(),
12110            "No signature help was called for"
12111        );
12112        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12113    });
12114    cx.run_until_parked();
12115    cx.update_editor(|editor, _, _| {
12116        assert!(
12117            !editor.signature_help_state.is_shown(),
12118            "No signature help should be shown when completions menu is open"
12119        );
12120    });
12121
12122    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12123        editor.context_menu_next(&Default::default(), window, cx);
12124        editor
12125            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12126            .unwrap()
12127    });
12128    cx.assert_editor_state(indoc! {"
12129        one.second_completionˇ
12130        two
12131        three
12132    "});
12133
12134    handle_resolve_completion_request(
12135        &mut cx,
12136        Some(vec![
12137            (
12138                //This overlaps with the primary completion edit which is
12139                //misbehavior from the LSP spec, test that we filter it out
12140                indoc! {"
12141                    one.second_ˇcompletion
12142                    two
12143                    threeˇ
12144                "},
12145                "overlapping additional edit",
12146            ),
12147            (
12148                indoc! {"
12149                    one.second_completion
12150                    two
12151                    threeˇ
12152                "},
12153                "\nadditional edit",
12154            ),
12155        ]),
12156    )
12157    .await;
12158    apply_additional_edits.await.unwrap();
12159    cx.assert_editor_state(indoc! {"
12160        one.second_completionˇ
12161        two
12162        three
12163        additional edit
12164    "});
12165
12166    cx.set_state(indoc! {"
12167        one.second_completion
12168        twoˇ
12169        threeˇ
12170        additional edit
12171    "});
12172    cx.simulate_keystroke(" ");
12173    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12174    cx.simulate_keystroke("s");
12175    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12176
12177    cx.assert_editor_state(indoc! {"
12178        one.second_completion
12179        two sˇ
12180        three sˇ
12181        additional edit
12182    "});
12183    handle_completion_request(
12184        indoc! {"
12185            one.second_completion
12186            two s
12187            three <s|>
12188            additional edit
12189        "},
12190        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12191        true,
12192        counter.clone(),
12193        &mut cx,
12194    )
12195    .await;
12196    cx.condition(|editor, _| editor.context_menu_visible())
12197        .await;
12198    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12199
12200    cx.simulate_keystroke("i");
12201
12202    handle_completion_request(
12203        indoc! {"
12204            one.second_completion
12205            two si
12206            three <si|>
12207            additional edit
12208        "},
12209        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12210        true,
12211        counter.clone(),
12212        &mut cx,
12213    )
12214    .await;
12215    cx.condition(|editor, _| editor.context_menu_visible())
12216        .await;
12217    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12218
12219    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12220        editor
12221            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12222            .unwrap()
12223    });
12224    cx.assert_editor_state(indoc! {"
12225        one.second_completion
12226        two sixth_completionˇ
12227        three sixth_completionˇ
12228        additional edit
12229    "});
12230
12231    apply_additional_edits.await.unwrap();
12232
12233    update_test_language_settings(&mut cx, |settings| {
12234        settings.defaults.show_completions_on_input = Some(false);
12235    });
12236    cx.set_state("editorˇ");
12237    cx.simulate_keystroke(".");
12238    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12239    cx.simulate_keystrokes("c l o");
12240    cx.assert_editor_state("editor.cloˇ");
12241    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12242    cx.update_editor(|editor, window, cx| {
12243        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12244    });
12245    handle_completion_request(
12246        "editor.<clo|>",
12247        vec!["close", "clobber"],
12248        true,
12249        counter.clone(),
12250        &mut cx,
12251    )
12252    .await;
12253    cx.condition(|editor, _| editor.context_menu_visible())
12254        .await;
12255    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12256
12257    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12258        editor
12259            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12260            .unwrap()
12261    });
12262    cx.assert_editor_state("editor.clobberˇ");
12263    handle_resolve_completion_request(&mut cx, None).await;
12264    apply_additional_edits.await.unwrap();
12265}
12266
12267#[gpui::test]
12268async fn test_completion_reuse(cx: &mut TestAppContext) {
12269    init_test(cx, |_| {});
12270
12271    let mut cx = EditorLspTestContext::new_rust(
12272        lsp::ServerCapabilities {
12273            completion_provider: Some(lsp::CompletionOptions {
12274                trigger_characters: Some(vec![".".to_string()]),
12275                ..Default::default()
12276            }),
12277            ..Default::default()
12278        },
12279        cx,
12280    )
12281    .await;
12282
12283    let counter = Arc::new(AtomicUsize::new(0));
12284    cx.set_state("objˇ");
12285    cx.simulate_keystroke(".");
12286
12287    // Initial completion request returns complete results
12288    let is_incomplete = false;
12289    handle_completion_request(
12290        "obj.|<>",
12291        vec!["a", "ab", "abc"],
12292        is_incomplete,
12293        counter.clone(),
12294        &mut cx,
12295    )
12296    .await;
12297    cx.run_until_parked();
12298    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12299    cx.assert_editor_state("obj.ˇ");
12300    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12301
12302    // Type "a" - filters existing completions
12303    cx.simulate_keystroke("a");
12304    cx.run_until_parked();
12305    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12306    cx.assert_editor_state("obj.aˇ");
12307    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12308
12309    // Type "b" - filters existing completions
12310    cx.simulate_keystroke("b");
12311    cx.run_until_parked();
12312    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12313    cx.assert_editor_state("obj.abˇ");
12314    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12315
12316    // Type "c" - filters existing completions
12317    cx.simulate_keystroke("c");
12318    cx.run_until_parked();
12319    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12320    cx.assert_editor_state("obj.abcˇ");
12321    check_displayed_completions(vec!["abc"], &mut cx);
12322
12323    // Backspace to delete "c" - filters existing completions
12324    cx.update_editor(|editor, window, cx| {
12325        editor.backspace(&Backspace, window, cx);
12326    });
12327    cx.run_until_parked();
12328    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12329    cx.assert_editor_state("obj.abˇ");
12330    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12331
12332    // Moving cursor to the left dismisses menu.
12333    cx.update_editor(|editor, window, cx| {
12334        editor.move_left(&MoveLeft, window, cx);
12335    });
12336    cx.run_until_parked();
12337    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12338    cx.assert_editor_state("obj.aˇb");
12339    cx.update_editor(|editor, _, _| {
12340        assert_eq!(editor.context_menu_visible(), false);
12341    });
12342
12343    // Type "b" - new request
12344    cx.simulate_keystroke("b");
12345    let is_incomplete = false;
12346    handle_completion_request(
12347        "obj.<ab|>a",
12348        vec!["ab", "abc"],
12349        is_incomplete,
12350        counter.clone(),
12351        &mut cx,
12352    )
12353    .await;
12354    cx.run_until_parked();
12355    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12356    cx.assert_editor_state("obj.abˇb");
12357    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12358
12359    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12360    cx.update_editor(|editor, window, cx| {
12361        editor.backspace(&Backspace, window, cx);
12362    });
12363    let is_incomplete = false;
12364    handle_completion_request(
12365        "obj.<a|>b",
12366        vec!["a", "ab", "abc"],
12367        is_incomplete,
12368        counter.clone(),
12369        &mut cx,
12370    )
12371    .await;
12372    cx.run_until_parked();
12373    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12374    cx.assert_editor_state("obj.aˇb");
12375    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12376
12377    // Backspace to delete "a" - dismisses menu.
12378    cx.update_editor(|editor, window, cx| {
12379        editor.backspace(&Backspace, window, cx);
12380    });
12381    cx.run_until_parked();
12382    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12383    cx.assert_editor_state("obj.ˇb");
12384    cx.update_editor(|editor, _, _| {
12385        assert_eq!(editor.context_menu_visible(), false);
12386    });
12387}
12388
12389#[gpui::test]
12390async fn test_word_completion(cx: &mut TestAppContext) {
12391    let lsp_fetch_timeout_ms = 10;
12392    init_test(cx, |language_settings| {
12393        language_settings.defaults.completions = Some(CompletionSettings {
12394            words: WordsCompletionMode::Fallback,
12395            lsp: true,
12396            lsp_fetch_timeout_ms: 10,
12397            lsp_insert_mode: LspInsertMode::Insert,
12398        });
12399    });
12400
12401    let mut cx = EditorLspTestContext::new_rust(
12402        lsp::ServerCapabilities {
12403            completion_provider: Some(lsp::CompletionOptions {
12404                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12405                ..lsp::CompletionOptions::default()
12406            }),
12407            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12408            ..lsp::ServerCapabilities::default()
12409        },
12410        cx,
12411    )
12412    .await;
12413
12414    let throttle_completions = Arc::new(AtomicBool::new(false));
12415
12416    let lsp_throttle_completions = throttle_completions.clone();
12417    let _completion_requests_handler =
12418        cx.lsp
12419            .server
12420            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12421                let lsp_throttle_completions = lsp_throttle_completions.clone();
12422                let cx = cx.clone();
12423                async move {
12424                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12425                        cx.background_executor()
12426                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12427                            .await;
12428                    }
12429                    Ok(Some(lsp::CompletionResponse::Array(vec![
12430                        lsp::CompletionItem {
12431                            label: "first".into(),
12432                            ..lsp::CompletionItem::default()
12433                        },
12434                        lsp::CompletionItem {
12435                            label: "last".into(),
12436                            ..lsp::CompletionItem::default()
12437                        },
12438                    ])))
12439                }
12440            });
12441
12442    cx.set_state(indoc! {"
12443        oneˇ
12444        two
12445        three
12446    "});
12447    cx.simulate_keystroke(".");
12448    cx.executor().run_until_parked();
12449    cx.condition(|editor, _| editor.context_menu_visible())
12450        .await;
12451    cx.update_editor(|editor, window, cx| {
12452        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12453        {
12454            assert_eq!(
12455                completion_menu_entries(&menu),
12456                &["first", "last"],
12457                "When LSP server is fast to reply, no fallback word completions are used"
12458            );
12459        } else {
12460            panic!("expected completion menu to be open");
12461        }
12462        editor.cancel(&Cancel, window, cx);
12463    });
12464    cx.executor().run_until_parked();
12465    cx.condition(|editor, _| !editor.context_menu_visible())
12466        .await;
12467
12468    throttle_completions.store(true, atomic::Ordering::Release);
12469    cx.simulate_keystroke(".");
12470    cx.executor()
12471        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12472    cx.executor().run_until_parked();
12473    cx.condition(|editor, _| editor.context_menu_visible())
12474        .await;
12475    cx.update_editor(|editor, _, _| {
12476        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12477        {
12478            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12479                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12480        } else {
12481            panic!("expected completion menu to be open");
12482        }
12483    });
12484}
12485
12486#[gpui::test]
12487async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12488    init_test(cx, |language_settings| {
12489        language_settings.defaults.completions = Some(CompletionSettings {
12490            words: WordsCompletionMode::Enabled,
12491            lsp: true,
12492            lsp_fetch_timeout_ms: 0,
12493            lsp_insert_mode: LspInsertMode::Insert,
12494        });
12495    });
12496
12497    let mut cx = EditorLspTestContext::new_rust(
12498        lsp::ServerCapabilities {
12499            completion_provider: Some(lsp::CompletionOptions {
12500                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12501                ..lsp::CompletionOptions::default()
12502            }),
12503            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12504            ..lsp::ServerCapabilities::default()
12505        },
12506        cx,
12507    )
12508    .await;
12509
12510    let _completion_requests_handler =
12511        cx.lsp
12512            .server
12513            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12514                Ok(Some(lsp::CompletionResponse::Array(vec![
12515                    lsp::CompletionItem {
12516                        label: "first".into(),
12517                        ..lsp::CompletionItem::default()
12518                    },
12519                    lsp::CompletionItem {
12520                        label: "last".into(),
12521                        ..lsp::CompletionItem::default()
12522                    },
12523                ])))
12524            });
12525
12526    cx.set_state(indoc! {"ˇ
12527        first
12528        last
12529        second
12530    "});
12531    cx.simulate_keystroke(".");
12532    cx.executor().run_until_parked();
12533    cx.condition(|editor, _| editor.context_menu_visible())
12534        .await;
12535    cx.update_editor(|editor, _, _| {
12536        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12537        {
12538            assert_eq!(
12539                completion_menu_entries(&menu),
12540                &["first", "last", "second"],
12541                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12542            );
12543        } else {
12544            panic!("expected completion menu to be open");
12545        }
12546    });
12547}
12548
12549#[gpui::test]
12550async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12551    init_test(cx, |language_settings| {
12552        language_settings.defaults.completions = Some(CompletionSettings {
12553            words: WordsCompletionMode::Disabled,
12554            lsp: true,
12555            lsp_fetch_timeout_ms: 0,
12556            lsp_insert_mode: LspInsertMode::Insert,
12557        });
12558    });
12559
12560    let mut cx = EditorLspTestContext::new_rust(
12561        lsp::ServerCapabilities {
12562            completion_provider: Some(lsp::CompletionOptions {
12563                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12564                ..lsp::CompletionOptions::default()
12565            }),
12566            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12567            ..lsp::ServerCapabilities::default()
12568        },
12569        cx,
12570    )
12571    .await;
12572
12573    let _completion_requests_handler =
12574        cx.lsp
12575            .server
12576            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12577                panic!("LSP completions should not be queried when dealing with word completions")
12578            });
12579
12580    cx.set_state(indoc! {"ˇ
12581        first
12582        last
12583        second
12584    "});
12585    cx.update_editor(|editor, window, cx| {
12586        editor.show_word_completions(&ShowWordCompletions, window, cx);
12587    });
12588    cx.executor().run_until_parked();
12589    cx.condition(|editor, _| editor.context_menu_visible())
12590        .await;
12591    cx.update_editor(|editor, _, _| {
12592        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12593        {
12594            assert_eq!(
12595                completion_menu_entries(&menu),
12596                &["first", "last", "second"],
12597                "`ShowWordCompletions` action should show word completions"
12598            );
12599        } else {
12600            panic!("expected completion menu to be open");
12601        }
12602    });
12603
12604    cx.simulate_keystroke("l");
12605    cx.executor().run_until_parked();
12606    cx.condition(|editor, _| editor.context_menu_visible())
12607        .await;
12608    cx.update_editor(|editor, _, _| {
12609        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12610        {
12611            assert_eq!(
12612                completion_menu_entries(&menu),
12613                &["last"],
12614                "After showing word completions, further editing should filter them and not query the LSP"
12615            );
12616        } else {
12617            panic!("expected completion menu to be open");
12618        }
12619    });
12620}
12621
12622#[gpui::test]
12623async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12624    init_test(cx, |language_settings| {
12625        language_settings.defaults.completions = Some(CompletionSettings {
12626            words: WordsCompletionMode::Fallback,
12627            lsp: false,
12628            lsp_fetch_timeout_ms: 0,
12629            lsp_insert_mode: LspInsertMode::Insert,
12630        });
12631    });
12632
12633    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12634
12635    cx.set_state(indoc! {"ˇ
12636        0_usize
12637        let
12638        33
12639        4.5f32
12640    "});
12641    cx.update_editor(|editor, window, cx| {
12642        editor.show_completions(&ShowCompletions::default(), window, cx);
12643    });
12644    cx.executor().run_until_parked();
12645    cx.condition(|editor, _| editor.context_menu_visible())
12646        .await;
12647    cx.update_editor(|editor, window, cx| {
12648        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12649        {
12650            assert_eq!(
12651                completion_menu_entries(&menu),
12652                &["let"],
12653                "With no digits in the completion query, no digits should be in the word completions"
12654            );
12655        } else {
12656            panic!("expected completion menu to be open");
12657        }
12658        editor.cancel(&Cancel, window, cx);
12659    });
12660
12661    cx.set_state(indoc! {"12662        0_usize
12663        let
12664        3
12665        33.35f32
12666    "});
12667    cx.update_editor(|editor, window, cx| {
12668        editor.show_completions(&ShowCompletions::default(), window, cx);
12669    });
12670    cx.executor().run_until_parked();
12671    cx.condition(|editor, _| editor.context_menu_visible())
12672        .await;
12673    cx.update_editor(|editor, _, _| {
12674        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12675        {
12676            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12677                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12678        } else {
12679            panic!("expected completion menu to be open");
12680        }
12681    });
12682}
12683
12684fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12685    let position = || lsp::Position {
12686        line: params.text_document_position.position.line,
12687        character: params.text_document_position.position.character,
12688    };
12689    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12690        range: lsp::Range {
12691            start: position(),
12692            end: position(),
12693        },
12694        new_text: text.to_string(),
12695    }))
12696}
12697
12698#[gpui::test]
12699async fn test_multiline_completion(cx: &mut TestAppContext) {
12700    init_test(cx, |_| {});
12701
12702    let fs = FakeFs::new(cx.executor());
12703    fs.insert_tree(
12704        path!("/a"),
12705        json!({
12706            "main.ts": "a",
12707        }),
12708    )
12709    .await;
12710
12711    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12712    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12713    let typescript_language = Arc::new(Language::new(
12714        LanguageConfig {
12715            name: "TypeScript".into(),
12716            matcher: LanguageMatcher {
12717                path_suffixes: vec!["ts".to_string()],
12718                ..LanguageMatcher::default()
12719            },
12720            line_comments: vec!["// ".into()],
12721            ..LanguageConfig::default()
12722        },
12723        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12724    ));
12725    language_registry.add(typescript_language.clone());
12726    let mut fake_servers = language_registry.register_fake_lsp(
12727        "TypeScript",
12728        FakeLspAdapter {
12729            capabilities: lsp::ServerCapabilities {
12730                completion_provider: Some(lsp::CompletionOptions {
12731                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12732                    ..lsp::CompletionOptions::default()
12733                }),
12734                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12735                ..lsp::ServerCapabilities::default()
12736            },
12737            // Emulate vtsls label generation
12738            label_for_completion: Some(Box::new(|item, _| {
12739                let text = if let Some(description) = item
12740                    .label_details
12741                    .as_ref()
12742                    .and_then(|label_details| label_details.description.as_ref())
12743                {
12744                    format!("{} {}", item.label, description)
12745                } else if let Some(detail) = &item.detail {
12746                    format!("{} {}", item.label, detail)
12747                } else {
12748                    item.label.clone()
12749                };
12750                let len = text.len();
12751                Some(language::CodeLabel {
12752                    text,
12753                    runs: Vec::new(),
12754                    filter_range: 0..len,
12755                })
12756            })),
12757            ..FakeLspAdapter::default()
12758        },
12759    );
12760    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12761    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12762    let worktree_id = workspace
12763        .update(cx, |workspace, _window, cx| {
12764            workspace.project().update(cx, |project, cx| {
12765                project.worktrees(cx).next().unwrap().read(cx).id()
12766            })
12767        })
12768        .unwrap();
12769    let _buffer = project
12770        .update(cx, |project, cx| {
12771            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12772        })
12773        .await
12774        .unwrap();
12775    let editor = workspace
12776        .update(cx, |workspace, window, cx| {
12777            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12778        })
12779        .unwrap()
12780        .await
12781        .unwrap()
12782        .downcast::<Editor>()
12783        .unwrap();
12784    let fake_server = fake_servers.next().await.unwrap();
12785
12786    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12787    let multiline_label_2 = "a\nb\nc\n";
12788    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12789    let multiline_description = "d\ne\nf\n";
12790    let multiline_detail_2 = "g\nh\ni\n";
12791
12792    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12793        move |params, _| async move {
12794            Ok(Some(lsp::CompletionResponse::Array(vec![
12795                lsp::CompletionItem {
12796                    label: multiline_label.to_string(),
12797                    text_edit: gen_text_edit(&params, "new_text_1"),
12798                    ..lsp::CompletionItem::default()
12799                },
12800                lsp::CompletionItem {
12801                    label: "single line label 1".to_string(),
12802                    detail: Some(multiline_detail.to_string()),
12803                    text_edit: gen_text_edit(&params, "new_text_2"),
12804                    ..lsp::CompletionItem::default()
12805                },
12806                lsp::CompletionItem {
12807                    label: "single line label 2".to_string(),
12808                    label_details: Some(lsp::CompletionItemLabelDetails {
12809                        description: Some(multiline_description.to_string()),
12810                        detail: None,
12811                    }),
12812                    text_edit: gen_text_edit(&params, "new_text_2"),
12813                    ..lsp::CompletionItem::default()
12814                },
12815                lsp::CompletionItem {
12816                    label: multiline_label_2.to_string(),
12817                    detail: Some(multiline_detail_2.to_string()),
12818                    text_edit: gen_text_edit(&params, "new_text_3"),
12819                    ..lsp::CompletionItem::default()
12820                },
12821                lsp::CompletionItem {
12822                    label: "Label with many     spaces and \t but without newlines".to_string(),
12823                    detail: Some(
12824                        "Details with many     spaces and \t but without newlines".to_string(),
12825                    ),
12826                    text_edit: gen_text_edit(&params, "new_text_4"),
12827                    ..lsp::CompletionItem::default()
12828                },
12829            ])))
12830        },
12831    );
12832
12833    editor.update_in(cx, |editor, window, cx| {
12834        cx.focus_self(window);
12835        editor.move_to_end(&MoveToEnd, window, cx);
12836        editor.handle_input(".", window, cx);
12837    });
12838    cx.run_until_parked();
12839    completion_handle.next().await.unwrap();
12840
12841    editor.update(cx, |editor, _| {
12842        assert!(editor.context_menu_visible());
12843        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12844        {
12845            let completion_labels = menu
12846                .completions
12847                .borrow()
12848                .iter()
12849                .map(|c| c.label.text.clone())
12850                .collect::<Vec<_>>();
12851            assert_eq!(
12852                completion_labels,
12853                &[
12854                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12855                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12856                    "single line label 2 d e f ",
12857                    "a b c g h i ",
12858                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12859                ],
12860                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12861            );
12862
12863            for completion in menu
12864                .completions
12865                .borrow()
12866                .iter() {
12867                    assert_eq!(
12868                        completion.label.filter_range,
12869                        0..completion.label.text.len(),
12870                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12871                    );
12872                }
12873        } else {
12874            panic!("expected completion menu to be open");
12875        }
12876    });
12877}
12878
12879#[gpui::test]
12880async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12881    init_test(cx, |_| {});
12882    let mut cx = EditorLspTestContext::new_rust(
12883        lsp::ServerCapabilities {
12884            completion_provider: Some(lsp::CompletionOptions {
12885                trigger_characters: Some(vec![".".to_string()]),
12886                ..Default::default()
12887            }),
12888            ..Default::default()
12889        },
12890        cx,
12891    )
12892    .await;
12893    cx.lsp
12894        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12895            Ok(Some(lsp::CompletionResponse::Array(vec![
12896                lsp::CompletionItem {
12897                    label: "first".into(),
12898                    ..Default::default()
12899                },
12900                lsp::CompletionItem {
12901                    label: "last".into(),
12902                    ..Default::default()
12903                },
12904            ])))
12905        });
12906    cx.set_state("variableˇ");
12907    cx.simulate_keystroke(".");
12908    cx.executor().run_until_parked();
12909
12910    cx.update_editor(|editor, _, _| {
12911        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12912        {
12913            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12914        } else {
12915            panic!("expected completion menu to be open");
12916        }
12917    });
12918
12919    cx.update_editor(|editor, window, cx| {
12920        editor.move_page_down(&MovePageDown::default(), window, cx);
12921        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12922        {
12923            assert!(
12924                menu.selected_item == 1,
12925                "expected PageDown to select the last item from the context menu"
12926            );
12927        } else {
12928            panic!("expected completion menu to stay open after PageDown");
12929        }
12930    });
12931
12932    cx.update_editor(|editor, window, cx| {
12933        editor.move_page_up(&MovePageUp::default(), window, cx);
12934        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12935        {
12936            assert!(
12937                menu.selected_item == 0,
12938                "expected PageUp to select the first item from the context menu"
12939            );
12940        } else {
12941            panic!("expected completion menu to stay open after PageUp");
12942        }
12943    });
12944}
12945
12946#[gpui::test]
12947async fn test_as_is_completions(cx: &mut TestAppContext) {
12948    init_test(cx, |_| {});
12949    let mut cx = EditorLspTestContext::new_rust(
12950        lsp::ServerCapabilities {
12951            completion_provider: Some(lsp::CompletionOptions {
12952                ..Default::default()
12953            }),
12954            ..Default::default()
12955        },
12956        cx,
12957    )
12958    .await;
12959    cx.lsp
12960        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12961            Ok(Some(lsp::CompletionResponse::Array(vec![
12962                lsp::CompletionItem {
12963                    label: "unsafe".into(),
12964                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12965                        range: lsp::Range {
12966                            start: lsp::Position {
12967                                line: 1,
12968                                character: 2,
12969                            },
12970                            end: lsp::Position {
12971                                line: 1,
12972                                character: 3,
12973                            },
12974                        },
12975                        new_text: "unsafe".to_string(),
12976                    })),
12977                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12978                    ..Default::default()
12979                },
12980            ])))
12981        });
12982    cx.set_state("fn a() {}\n");
12983    cx.executor().run_until_parked();
12984    cx.update_editor(|editor, window, cx| {
12985        editor.show_completions(
12986            &ShowCompletions {
12987                trigger: Some("\n".into()),
12988            },
12989            window,
12990            cx,
12991        );
12992    });
12993    cx.executor().run_until_parked();
12994
12995    cx.update_editor(|editor, window, cx| {
12996        editor.confirm_completion(&Default::default(), window, cx)
12997    });
12998    cx.executor().run_until_parked();
12999    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13000}
13001
13002#[gpui::test]
13003async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13004    init_test(cx, |_| {});
13005
13006    let mut cx = EditorLspTestContext::new_rust(
13007        lsp::ServerCapabilities {
13008            completion_provider: Some(lsp::CompletionOptions {
13009                trigger_characters: Some(vec![".".to_string()]),
13010                resolve_provider: Some(true),
13011                ..Default::default()
13012            }),
13013            ..Default::default()
13014        },
13015        cx,
13016    )
13017    .await;
13018
13019    cx.set_state("fn main() { let a = 2ˇ; }");
13020    cx.simulate_keystroke(".");
13021    let completion_item = lsp::CompletionItem {
13022        label: "Some".into(),
13023        kind: Some(lsp::CompletionItemKind::SNIPPET),
13024        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13025        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13026            kind: lsp::MarkupKind::Markdown,
13027            value: "```rust\nSome(2)\n```".to_string(),
13028        })),
13029        deprecated: Some(false),
13030        sort_text: Some("Some".to_string()),
13031        filter_text: Some("Some".to_string()),
13032        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13033        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13034            range: lsp::Range {
13035                start: lsp::Position {
13036                    line: 0,
13037                    character: 22,
13038                },
13039                end: lsp::Position {
13040                    line: 0,
13041                    character: 22,
13042                },
13043            },
13044            new_text: "Some(2)".to_string(),
13045        })),
13046        additional_text_edits: Some(vec![lsp::TextEdit {
13047            range: lsp::Range {
13048                start: lsp::Position {
13049                    line: 0,
13050                    character: 20,
13051                },
13052                end: lsp::Position {
13053                    line: 0,
13054                    character: 22,
13055                },
13056            },
13057            new_text: "".to_string(),
13058        }]),
13059        ..Default::default()
13060    };
13061
13062    let closure_completion_item = completion_item.clone();
13063    let counter = Arc::new(AtomicUsize::new(0));
13064    let counter_clone = counter.clone();
13065    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13066        let task_completion_item = closure_completion_item.clone();
13067        counter_clone.fetch_add(1, atomic::Ordering::Release);
13068        async move {
13069            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13070                is_incomplete: true,
13071                item_defaults: None,
13072                items: vec![task_completion_item],
13073            })))
13074        }
13075    });
13076
13077    cx.condition(|editor, _| editor.context_menu_visible())
13078        .await;
13079    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13080    assert!(request.next().await.is_some());
13081    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13082
13083    cx.simulate_keystrokes("S o m");
13084    cx.condition(|editor, _| editor.context_menu_visible())
13085        .await;
13086    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13087    assert!(request.next().await.is_some());
13088    assert!(request.next().await.is_some());
13089    assert!(request.next().await.is_some());
13090    request.close();
13091    assert!(request.next().await.is_none());
13092    assert_eq!(
13093        counter.load(atomic::Ordering::Acquire),
13094        4,
13095        "With the completions menu open, only one LSP request should happen per input"
13096    );
13097}
13098
13099#[gpui::test]
13100async fn test_toggle_comment(cx: &mut TestAppContext) {
13101    init_test(cx, |_| {});
13102    let mut cx = EditorTestContext::new(cx).await;
13103    let language = Arc::new(Language::new(
13104        LanguageConfig {
13105            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13106            ..Default::default()
13107        },
13108        Some(tree_sitter_rust::LANGUAGE.into()),
13109    ));
13110    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13111
13112    // If multiple selections intersect a line, the line is only toggled once.
13113    cx.set_state(indoc! {"
13114        fn a() {
13115            «//b();
13116            ˇ»// «c();
13117            //ˇ»  d();
13118        }
13119    "});
13120
13121    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13122
13123    cx.assert_editor_state(indoc! {"
13124        fn a() {
13125            «b();
13126            c();
13127            ˇ» d();
13128        }
13129    "});
13130
13131    // The comment prefix is inserted at the same column for every line in a
13132    // selection.
13133    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13134
13135    cx.assert_editor_state(indoc! {"
13136        fn a() {
13137            // «b();
13138            // c();
13139            ˇ»//  d();
13140        }
13141    "});
13142
13143    // If a selection ends at the beginning of a line, that line is not toggled.
13144    cx.set_selections_state(indoc! {"
13145        fn a() {
13146            // b();
13147            «// c();
13148        ˇ»    //  d();
13149        }
13150    "});
13151
13152    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13153
13154    cx.assert_editor_state(indoc! {"
13155        fn a() {
13156            // b();
13157            «c();
13158        ˇ»    //  d();
13159        }
13160    "});
13161
13162    // If a selection span a single line and is empty, the line is toggled.
13163    cx.set_state(indoc! {"
13164        fn a() {
13165            a();
13166            b();
13167        ˇ
13168        }
13169    "});
13170
13171    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13172
13173    cx.assert_editor_state(indoc! {"
13174        fn a() {
13175            a();
13176            b();
13177        //•ˇ
13178        }
13179    "});
13180
13181    // If a selection span multiple lines, empty lines are not toggled.
13182    cx.set_state(indoc! {"
13183        fn a() {
13184            «a();
13185
13186            c();ˇ»
13187        }
13188    "});
13189
13190    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13191
13192    cx.assert_editor_state(indoc! {"
13193        fn a() {
13194            // «a();
13195
13196            // c();ˇ»
13197        }
13198    "});
13199
13200    // If a selection includes multiple comment prefixes, all lines are uncommented.
13201    cx.set_state(indoc! {"
13202        fn a() {
13203            «// a();
13204            /// b();
13205            //! c();ˇ»
13206        }
13207    "});
13208
13209    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13210
13211    cx.assert_editor_state(indoc! {"
13212        fn a() {
13213            «a();
13214            b();
13215            c();ˇ»
13216        }
13217    "});
13218}
13219
13220#[gpui::test]
13221async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13222    init_test(cx, |_| {});
13223    let mut cx = EditorTestContext::new(cx).await;
13224    let language = Arc::new(Language::new(
13225        LanguageConfig {
13226            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13227            ..Default::default()
13228        },
13229        Some(tree_sitter_rust::LANGUAGE.into()),
13230    ));
13231    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13232
13233    let toggle_comments = &ToggleComments {
13234        advance_downwards: false,
13235        ignore_indent: true,
13236    };
13237
13238    // If multiple selections intersect a line, the line is only toggled once.
13239    cx.set_state(indoc! {"
13240        fn a() {
13241        //    «b();
13242        //    c();
13243        //    ˇ» d();
13244        }
13245    "});
13246
13247    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13248
13249    cx.assert_editor_state(indoc! {"
13250        fn a() {
13251            «b();
13252            c();
13253            ˇ» d();
13254        }
13255    "});
13256
13257    // The comment prefix is inserted at the beginning of each line
13258    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13259
13260    cx.assert_editor_state(indoc! {"
13261        fn a() {
13262        //    «b();
13263        //    c();
13264        //    ˇ» d();
13265        }
13266    "});
13267
13268    // If a selection ends at the beginning of a line, that line is not toggled.
13269    cx.set_selections_state(indoc! {"
13270        fn a() {
13271        //    b();
13272        //    «c();
13273        ˇ»//     d();
13274        }
13275    "});
13276
13277    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13278
13279    cx.assert_editor_state(indoc! {"
13280        fn a() {
13281        //    b();
13282            «c();
13283        ˇ»//     d();
13284        }
13285    "});
13286
13287    // If a selection span a single line and is empty, the line is toggled.
13288    cx.set_state(indoc! {"
13289        fn a() {
13290            a();
13291            b();
13292        ˇ
13293        }
13294    "});
13295
13296    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13297
13298    cx.assert_editor_state(indoc! {"
13299        fn a() {
13300            a();
13301            b();
13302        //ˇ
13303        }
13304    "});
13305
13306    // If a selection span multiple lines, empty lines are not toggled.
13307    cx.set_state(indoc! {"
13308        fn a() {
13309            «a();
13310
13311            c();ˇ»
13312        }
13313    "});
13314
13315    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13316
13317    cx.assert_editor_state(indoc! {"
13318        fn a() {
13319        //    «a();
13320
13321        //    c();ˇ»
13322        }
13323    "});
13324
13325    // If a selection includes multiple comment prefixes, all lines are uncommented.
13326    cx.set_state(indoc! {"
13327        fn a() {
13328        //    «a();
13329        ///    b();
13330        //!    c();ˇ»
13331        }
13332    "});
13333
13334    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13335
13336    cx.assert_editor_state(indoc! {"
13337        fn a() {
13338            «a();
13339            b();
13340            c();ˇ»
13341        }
13342    "});
13343}
13344
13345#[gpui::test]
13346async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13347    init_test(cx, |_| {});
13348
13349    let language = Arc::new(Language::new(
13350        LanguageConfig {
13351            line_comments: vec!["// ".into()],
13352            ..Default::default()
13353        },
13354        Some(tree_sitter_rust::LANGUAGE.into()),
13355    ));
13356
13357    let mut cx = EditorTestContext::new(cx).await;
13358
13359    cx.language_registry().add(language.clone());
13360    cx.update_buffer(|buffer, cx| {
13361        buffer.set_language(Some(language), cx);
13362    });
13363
13364    let toggle_comments = &ToggleComments {
13365        advance_downwards: true,
13366        ignore_indent: false,
13367    };
13368
13369    // Single cursor on one line -> advance
13370    // Cursor moves horizontally 3 characters as well on non-blank line
13371    cx.set_state(indoc!(
13372        "fn a() {
13373             ˇdog();
13374             cat();
13375        }"
13376    ));
13377    cx.update_editor(|editor, window, cx| {
13378        editor.toggle_comments(toggle_comments, window, cx);
13379    });
13380    cx.assert_editor_state(indoc!(
13381        "fn a() {
13382             // dog();
13383             catˇ();
13384        }"
13385    ));
13386
13387    // Single selection on one line -> don't advance
13388    cx.set_state(indoc!(
13389        "fn a() {
13390             «dog()ˇ»;
13391             cat();
13392        }"
13393    ));
13394    cx.update_editor(|editor, window, cx| {
13395        editor.toggle_comments(toggle_comments, window, cx);
13396    });
13397    cx.assert_editor_state(indoc!(
13398        "fn a() {
13399             // «dog()ˇ»;
13400             cat();
13401        }"
13402    ));
13403
13404    // Multiple cursors on one line -> advance
13405    cx.set_state(indoc!(
13406        "fn a() {
13407             ˇdˇog();
13408             cat();
13409        }"
13410    ));
13411    cx.update_editor(|editor, window, cx| {
13412        editor.toggle_comments(toggle_comments, window, cx);
13413    });
13414    cx.assert_editor_state(indoc!(
13415        "fn a() {
13416             // dog();
13417             catˇ(ˇ);
13418        }"
13419    ));
13420
13421    // Multiple cursors on one line, with selection -> don't advance
13422    cx.set_state(indoc!(
13423        "fn a() {
13424             ˇdˇog«()ˇ»;
13425             cat();
13426        }"
13427    ));
13428    cx.update_editor(|editor, window, cx| {
13429        editor.toggle_comments(toggle_comments, window, cx);
13430    });
13431    cx.assert_editor_state(indoc!(
13432        "fn a() {
13433             // ˇdˇog«()ˇ»;
13434             cat();
13435        }"
13436    ));
13437
13438    // Single cursor on one line -> advance
13439    // Cursor moves to column 0 on blank line
13440    cx.set_state(indoc!(
13441        "fn a() {
13442             ˇdog();
13443
13444             cat();
13445        }"
13446    ));
13447    cx.update_editor(|editor, window, cx| {
13448        editor.toggle_comments(toggle_comments, window, cx);
13449    });
13450    cx.assert_editor_state(indoc!(
13451        "fn a() {
13452             // dog();
13453        ˇ
13454             cat();
13455        }"
13456    ));
13457
13458    // Single cursor on one line -> advance
13459    // Cursor starts and ends at column 0
13460    cx.set_state(indoc!(
13461        "fn a() {
13462         ˇ    dog();
13463             cat();
13464        }"
13465    ));
13466    cx.update_editor(|editor, window, cx| {
13467        editor.toggle_comments(toggle_comments, window, cx);
13468    });
13469    cx.assert_editor_state(indoc!(
13470        "fn a() {
13471             // dog();
13472         ˇ    cat();
13473        }"
13474    ));
13475}
13476
13477#[gpui::test]
13478async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13479    init_test(cx, |_| {});
13480
13481    let mut cx = EditorTestContext::new(cx).await;
13482
13483    let html_language = Arc::new(
13484        Language::new(
13485            LanguageConfig {
13486                name: "HTML".into(),
13487                block_comment: Some(("<!-- ".into(), " -->".into())),
13488                ..Default::default()
13489            },
13490            Some(tree_sitter_html::LANGUAGE.into()),
13491        )
13492        .with_injection_query(
13493            r#"
13494            (script_element
13495                (raw_text) @injection.content
13496                (#set! injection.language "javascript"))
13497            "#,
13498        )
13499        .unwrap(),
13500    );
13501
13502    let javascript_language = Arc::new(Language::new(
13503        LanguageConfig {
13504            name: "JavaScript".into(),
13505            line_comments: vec!["// ".into()],
13506            ..Default::default()
13507        },
13508        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13509    ));
13510
13511    cx.language_registry().add(html_language.clone());
13512    cx.language_registry().add(javascript_language.clone());
13513    cx.update_buffer(|buffer, cx| {
13514        buffer.set_language(Some(html_language), cx);
13515    });
13516
13517    // Toggle comments for empty selections
13518    cx.set_state(
13519        &r#"
13520            <p>A</p>ˇ
13521            <p>B</p>ˇ
13522            <p>C</p>ˇ
13523        "#
13524        .unindent(),
13525    );
13526    cx.update_editor(|editor, window, cx| {
13527        editor.toggle_comments(&ToggleComments::default(), window, cx)
13528    });
13529    cx.assert_editor_state(
13530        &r#"
13531            <!-- <p>A</p>ˇ -->
13532            <!-- <p>B</p>ˇ -->
13533            <!-- <p>C</p>ˇ -->
13534        "#
13535        .unindent(),
13536    );
13537    cx.update_editor(|editor, window, cx| {
13538        editor.toggle_comments(&ToggleComments::default(), window, cx)
13539    });
13540    cx.assert_editor_state(
13541        &r#"
13542            <p>A</p>ˇ
13543            <p>B</p>ˇ
13544            <p>C</p>ˇ
13545        "#
13546        .unindent(),
13547    );
13548
13549    // Toggle comments for mixture of empty and non-empty selections, where
13550    // multiple selections occupy a given line.
13551    cx.set_state(
13552        &r#"
13553            <p>A«</p>
13554            <p>ˇ»B</p>ˇ
13555            <p>C«</p>
13556            <p>ˇ»D</p>ˇ
13557        "#
13558        .unindent(),
13559    );
13560
13561    cx.update_editor(|editor, window, cx| {
13562        editor.toggle_comments(&ToggleComments::default(), window, cx)
13563    });
13564    cx.assert_editor_state(
13565        &r#"
13566            <!-- <p>A«</p>
13567            <p>ˇ»B</p>ˇ -->
13568            <!-- <p>C«</p>
13569            <p>ˇ»D</p>ˇ -->
13570        "#
13571        .unindent(),
13572    );
13573    cx.update_editor(|editor, window, cx| {
13574        editor.toggle_comments(&ToggleComments::default(), window, cx)
13575    });
13576    cx.assert_editor_state(
13577        &r#"
13578            <p>A«</p>
13579            <p>ˇ»B</p>ˇ
13580            <p>C«</p>
13581            <p>ˇ»D</p>ˇ
13582        "#
13583        .unindent(),
13584    );
13585
13586    // Toggle comments when different languages are active for different
13587    // selections.
13588    cx.set_state(
13589        &r#"
13590            ˇ<script>
13591                ˇvar x = new Y();
13592            ˇ</script>
13593        "#
13594        .unindent(),
13595    );
13596    cx.executor().run_until_parked();
13597    cx.update_editor(|editor, window, cx| {
13598        editor.toggle_comments(&ToggleComments::default(), window, cx)
13599    });
13600    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13601    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13602    cx.assert_editor_state(
13603        &r#"
13604            <!-- ˇ<script> -->
13605                // ˇvar x = new Y();
13606            <!-- ˇ</script> -->
13607        "#
13608        .unindent(),
13609    );
13610}
13611
13612#[gpui::test]
13613fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13614    init_test(cx, |_| {});
13615
13616    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13617    let multibuffer = cx.new(|cx| {
13618        let mut multibuffer = MultiBuffer::new(ReadWrite);
13619        multibuffer.push_excerpts(
13620            buffer.clone(),
13621            [
13622                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13623                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13624            ],
13625            cx,
13626        );
13627        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13628        multibuffer
13629    });
13630
13631    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13632    editor.update_in(cx, |editor, window, cx| {
13633        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13635            s.select_ranges([
13636                Point::new(0, 0)..Point::new(0, 0),
13637                Point::new(1, 0)..Point::new(1, 0),
13638            ])
13639        });
13640
13641        editor.handle_input("X", window, cx);
13642        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13643        assert_eq!(
13644            editor.selections.ranges(cx),
13645            [
13646                Point::new(0, 1)..Point::new(0, 1),
13647                Point::new(1, 1)..Point::new(1, 1),
13648            ]
13649        );
13650
13651        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13652        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13653            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13654        });
13655        editor.backspace(&Default::default(), window, cx);
13656        assert_eq!(editor.text(cx), "Xa\nbbb");
13657        assert_eq!(
13658            editor.selections.ranges(cx),
13659            [Point::new(1, 0)..Point::new(1, 0)]
13660        );
13661
13662        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13663            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13664        });
13665        editor.backspace(&Default::default(), window, cx);
13666        assert_eq!(editor.text(cx), "X\nbb");
13667        assert_eq!(
13668            editor.selections.ranges(cx),
13669            [Point::new(0, 1)..Point::new(0, 1)]
13670        );
13671    });
13672}
13673
13674#[gpui::test]
13675fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13676    init_test(cx, |_| {});
13677
13678    let markers = vec![('[', ']').into(), ('(', ')').into()];
13679    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13680        indoc! {"
13681            [aaaa
13682            (bbbb]
13683            cccc)",
13684        },
13685        markers.clone(),
13686    );
13687    let excerpt_ranges = markers.into_iter().map(|marker| {
13688        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13689        ExcerptRange::new(context.clone())
13690    });
13691    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13692    let multibuffer = cx.new(|cx| {
13693        let mut multibuffer = MultiBuffer::new(ReadWrite);
13694        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13695        multibuffer
13696    });
13697
13698    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13699    editor.update_in(cx, |editor, window, cx| {
13700        let (expected_text, selection_ranges) = marked_text_ranges(
13701            indoc! {"
13702                aaaa
13703                bˇbbb
13704                bˇbbˇb
13705                cccc"
13706            },
13707            true,
13708        );
13709        assert_eq!(editor.text(cx), expected_text);
13710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13711            s.select_ranges(selection_ranges)
13712        });
13713
13714        editor.handle_input("X", window, cx);
13715
13716        let (expected_text, expected_selections) = marked_text_ranges(
13717            indoc! {"
13718                aaaa
13719                bXˇbbXb
13720                bXˇbbXˇb
13721                cccc"
13722            },
13723            false,
13724        );
13725        assert_eq!(editor.text(cx), expected_text);
13726        assert_eq!(editor.selections.ranges(cx), expected_selections);
13727
13728        editor.newline(&Newline, window, cx);
13729        let (expected_text, expected_selections) = marked_text_ranges(
13730            indoc! {"
13731                aaaa
13732                bX
13733                ˇbbX
13734                b
13735                bX
13736                ˇbbX
13737                ˇb
13738                cccc"
13739            },
13740            false,
13741        );
13742        assert_eq!(editor.text(cx), expected_text);
13743        assert_eq!(editor.selections.ranges(cx), expected_selections);
13744    });
13745}
13746
13747#[gpui::test]
13748fn test_refresh_selections(cx: &mut TestAppContext) {
13749    init_test(cx, |_| {});
13750
13751    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13752    let mut excerpt1_id = None;
13753    let multibuffer = cx.new(|cx| {
13754        let mut multibuffer = MultiBuffer::new(ReadWrite);
13755        excerpt1_id = multibuffer
13756            .push_excerpts(
13757                buffer.clone(),
13758                [
13759                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13760                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13761                ],
13762                cx,
13763            )
13764            .into_iter()
13765            .next();
13766        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13767        multibuffer
13768    });
13769
13770    let editor = cx.add_window(|window, cx| {
13771        let mut editor = build_editor(multibuffer.clone(), window, cx);
13772        let snapshot = editor.snapshot(window, cx);
13773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13774            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13775        });
13776        editor.begin_selection(
13777            Point::new(2, 1).to_display_point(&snapshot),
13778            true,
13779            1,
13780            window,
13781            cx,
13782        );
13783        assert_eq!(
13784            editor.selections.ranges(cx),
13785            [
13786                Point::new(1, 3)..Point::new(1, 3),
13787                Point::new(2, 1)..Point::new(2, 1),
13788            ]
13789        );
13790        editor
13791    });
13792
13793    // Refreshing selections is a no-op when excerpts haven't changed.
13794    _ = editor.update(cx, |editor, window, cx| {
13795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13796        assert_eq!(
13797            editor.selections.ranges(cx),
13798            [
13799                Point::new(1, 3)..Point::new(1, 3),
13800                Point::new(2, 1)..Point::new(2, 1),
13801            ]
13802        );
13803    });
13804
13805    multibuffer.update(cx, |multibuffer, cx| {
13806        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13807    });
13808    _ = editor.update(cx, |editor, window, cx| {
13809        // Removing an excerpt causes the first selection to become degenerate.
13810        assert_eq!(
13811            editor.selections.ranges(cx),
13812            [
13813                Point::new(0, 0)..Point::new(0, 0),
13814                Point::new(0, 1)..Point::new(0, 1)
13815            ]
13816        );
13817
13818        // Refreshing selections will relocate the first selection to the original buffer
13819        // location.
13820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13821        assert_eq!(
13822            editor.selections.ranges(cx),
13823            [
13824                Point::new(0, 1)..Point::new(0, 1),
13825                Point::new(0, 3)..Point::new(0, 3)
13826            ]
13827        );
13828        assert!(editor.selections.pending_anchor().is_some());
13829    });
13830}
13831
13832#[gpui::test]
13833fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13834    init_test(cx, |_| {});
13835
13836    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13837    let mut excerpt1_id = None;
13838    let multibuffer = cx.new(|cx| {
13839        let mut multibuffer = MultiBuffer::new(ReadWrite);
13840        excerpt1_id = multibuffer
13841            .push_excerpts(
13842                buffer.clone(),
13843                [
13844                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13845                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13846                ],
13847                cx,
13848            )
13849            .into_iter()
13850            .next();
13851        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13852        multibuffer
13853    });
13854
13855    let editor = cx.add_window(|window, cx| {
13856        let mut editor = build_editor(multibuffer.clone(), window, cx);
13857        let snapshot = editor.snapshot(window, cx);
13858        editor.begin_selection(
13859            Point::new(1, 3).to_display_point(&snapshot),
13860            false,
13861            1,
13862            window,
13863            cx,
13864        );
13865        assert_eq!(
13866            editor.selections.ranges(cx),
13867            [Point::new(1, 3)..Point::new(1, 3)]
13868        );
13869        editor
13870    });
13871
13872    multibuffer.update(cx, |multibuffer, cx| {
13873        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13874    });
13875    _ = editor.update(cx, |editor, window, cx| {
13876        assert_eq!(
13877            editor.selections.ranges(cx),
13878            [Point::new(0, 0)..Point::new(0, 0)]
13879        );
13880
13881        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13883        assert_eq!(
13884            editor.selections.ranges(cx),
13885            [Point::new(0, 3)..Point::new(0, 3)]
13886        );
13887        assert!(editor.selections.pending_anchor().is_some());
13888    });
13889}
13890
13891#[gpui::test]
13892async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13893    init_test(cx, |_| {});
13894
13895    let language = Arc::new(
13896        Language::new(
13897            LanguageConfig {
13898                brackets: BracketPairConfig {
13899                    pairs: vec![
13900                        BracketPair {
13901                            start: "{".to_string(),
13902                            end: "}".to_string(),
13903                            close: true,
13904                            surround: true,
13905                            newline: true,
13906                        },
13907                        BracketPair {
13908                            start: "/* ".to_string(),
13909                            end: " */".to_string(),
13910                            close: true,
13911                            surround: true,
13912                            newline: true,
13913                        },
13914                    ],
13915                    ..Default::default()
13916                },
13917                ..Default::default()
13918            },
13919            Some(tree_sitter_rust::LANGUAGE.into()),
13920        )
13921        .with_indents_query("")
13922        .unwrap(),
13923    );
13924
13925    let text = concat!(
13926        "{   }\n",     //
13927        "  x\n",       //
13928        "  /*   */\n", //
13929        "x\n",         //
13930        "{{} }\n",     //
13931    );
13932
13933    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13934    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13935    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13936    editor
13937        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13938        .await;
13939
13940    editor.update_in(cx, |editor, window, cx| {
13941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13942            s.select_display_ranges([
13943                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13944                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13945                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13946            ])
13947        });
13948        editor.newline(&Newline, window, cx);
13949
13950        assert_eq!(
13951            editor.buffer().read(cx).read(cx).text(),
13952            concat!(
13953                "{ \n",    // Suppress rustfmt
13954                "\n",      //
13955                "}\n",     //
13956                "  x\n",   //
13957                "  /* \n", //
13958                "  \n",    //
13959                "  */\n",  //
13960                "x\n",     //
13961                "{{} \n",  //
13962                "}\n",     //
13963            )
13964        );
13965    });
13966}
13967
13968#[gpui::test]
13969fn test_highlighted_ranges(cx: &mut TestAppContext) {
13970    init_test(cx, |_| {});
13971
13972    let editor = cx.add_window(|window, cx| {
13973        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13974        build_editor(buffer.clone(), window, cx)
13975    });
13976
13977    _ = editor.update(cx, |editor, window, cx| {
13978        struct Type1;
13979        struct Type2;
13980
13981        let buffer = editor.buffer.read(cx).snapshot(cx);
13982
13983        let anchor_range =
13984            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13985
13986        editor.highlight_background::<Type1>(
13987            &[
13988                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13989                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13990                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13991                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13992            ],
13993            |_| Hsla::red(),
13994            cx,
13995        );
13996        editor.highlight_background::<Type2>(
13997            &[
13998                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13999                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14000                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14001                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14002            ],
14003            |_| Hsla::green(),
14004            cx,
14005        );
14006
14007        let snapshot = editor.snapshot(window, cx);
14008        let mut highlighted_ranges = editor.background_highlights_in_range(
14009            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14010            &snapshot,
14011            cx.theme(),
14012        );
14013        // Enforce a consistent ordering based on color without relying on the ordering of the
14014        // highlight's `TypeId` which is non-executor.
14015        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14016        assert_eq!(
14017            highlighted_ranges,
14018            &[
14019                (
14020                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14021                    Hsla::red(),
14022                ),
14023                (
14024                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14025                    Hsla::red(),
14026                ),
14027                (
14028                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14029                    Hsla::green(),
14030                ),
14031                (
14032                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14033                    Hsla::green(),
14034                ),
14035            ]
14036        );
14037        assert_eq!(
14038            editor.background_highlights_in_range(
14039                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14040                &snapshot,
14041                cx.theme(),
14042            ),
14043            &[(
14044                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14045                Hsla::red(),
14046            )]
14047        );
14048    });
14049}
14050
14051#[gpui::test]
14052async fn test_following(cx: &mut TestAppContext) {
14053    init_test(cx, |_| {});
14054
14055    let fs = FakeFs::new(cx.executor());
14056    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14057
14058    let buffer = project.update(cx, |project, cx| {
14059        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14060        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14061    });
14062    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14063    let follower = cx.update(|cx| {
14064        cx.open_window(
14065            WindowOptions {
14066                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14067                    gpui::Point::new(px(0.), px(0.)),
14068                    gpui::Point::new(px(10.), px(80.)),
14069                ))),
14070                ..Default::default()
14071            },
14072            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14073        )
14074        .unwrap()
14075    });
14076
14077    let is_still_following = Rc::new(RefCell::new(true));
14078    let follower_edit_event_count = Rc::new(RefCell::new(0));
14079    let pending_update = Rc::new(RefCell::new(None));
14080    let leader_entity = leader.root(cx).unwrap();
14081    let follower_entity = follower.root(cx).unwrap();
14082    _ = follower.update(cx, {
14083        let update = pending_update.clone();
14084        let is_still_following = is_still_following.clone();
14085        let follower_edit_event_count = follower_edit_event_count.clone();
14086        |_, window, cx| {
14087            cx.subscribe_in(
14088                &leader_entity,
14089                window,
14090                move |_, leader, event, window, cx| {
14091                    leader.read(cx).add_event_to_update_proto(
14092                        event,
14093                        &mut update.borrow_mut(),
14094                        window,
14095                        cx,
14096                    );
14097                },
14098            )
14099            .detach();
14100
14101            cx.subscribe_in(
14102                &follower_entity,
14103                window,
14104                move |_, _, event: &EditorEvent, _window, _cx| {
14105                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14106                        *is_still_following.borrow_mut() = false;
14107                    }
14108
14109                    if let EditorEvent::BufferEdited = event {
14110                        *follower_edit_event_count.borrow_mut() += 1;
14111                    }
14112                },
14113            )
14114            .detach();
14115        }
14116    });
14117
14118    // Update the selections only
14119    _ = leader.update(cx, |leader, window, cx| {
14120        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14121            s.select_ranges([1..1])
14122        });
14123    });
14124    follower
14125        .update(cx, |follower, window, cx| {
14126            follower.apply_update_proto(
14127                &project,
14128                pending_update.borrow_mut().take().unwrap(),
14129                window,
14130                cx,
14131            )
14132        })
14133        .unwrap()
14134        .await
14135        .unwrap();
14136    _ = follower.update(cx, |follower, _, cx| {
14137        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14138    });
14139    assert!(*is_still_following.borrow());
14140    assert_eq!(*follower_edit_event_count.borrow(), 0);
14141
14142    // Update the scroll position only
14143    _ = leader.update(cx, |leader, window, cx| {
14144        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14145    });
14146    follower
14147        .update(cx, |follower, window, cx| {
14148            follower.apply_update_proto(
14149                &project,
14150                pending_update.borrow_mut().take().unwrap(),
14151                window,
14152                cx,
14153            )
14154        })
14155        .unwrap()
14156        .await
14157        .unwrap();
14158    assert_eq!(
14159        follower
14160            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14161            .unwrap(),
14162        gpui::Point::new(1.5, 3.5)
14163    );
14164    assert!(*is_still_following.borrow());
14165    assert_eq!(*follower_edit_event_count.borrow(), 0);
14166
14167    // Update the selections and scroll position. The follower's scroll position is updated
14168    // via autoscroll, not via the leader's exact scroll position.
14169    _ = leader.update(cx, |leader, window, cx| {
14170        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14171            s.select_ranges([0..0])
14172        });
14173        leader.request_autoscroll(Autoscroll::newest(), cx);
14174        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14175    });
14176    follower
14177        .update(cx, |follower, window, cx| {
14178            follower.apply_update_proto(
14179                &project,
14180                pending_update.borrow_mut().take().unwrap(),
14181                window,
14182                cx,
14183            )
14184        })
14185        .unwrap()
14186        .await
14187        .unwrap();
14188    _ = follower.update(cx, |follower, _, cx| {
14189        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14190        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14191    });
14192    assert!(*is_still_following.borrow());
14193
14194    // Creating a pending selection that precedes another selection
14195    _ = leader.update(cx, |leader, window, cx| {
14196        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14197            s.select_ranges([1..1])
14198        });
14199        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14200    });
14201    follower
14202        .update(cx, |follower, window, cx| {
14203            follower.apply_update_proto(
14204                &project,
14205                pending_update.borrow_mut().take().unwrap(),
14206                window,
14207                cx,
14208            )
14209        })
14210        .unwrap()
14211        .await
14212        .unwrap();
14213    _ = follower.update(cx, |follower, _, cx| {
14214        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14215    });
14216    assert!(*is_still_following.borrow());
14217
14218    // Extend the pending selection so that it surrounds another selection
14219    _ = leader.update(cx, |leader, window, cx| {
14220        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14221    });
14222    follower
14223        .update(cx, |follower, window, cx| {
14224            follower.apply_update_proto(
14225                &project,
14226                pending_update.borrow_mut().take().unwrap(),
14227                window,
14228                cx,
14229            )
14230        })
14231        .unwrap()
14232        .await
14233        .unwrap();
14234    _ = follower.update(cx, |follower, _, cx| {
14235        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14236    });
14237
14238    // Scrolling locally breaks the follow
14239    _ = follower.update(cx, |follower, window, cx| {
14240        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14241        follower.set_scroll_anchor(
14242            ScrollAnchor {
14243                anchor: top_anchor,
14244                offset: gpui::Point::new(0.0, 0.5),
14245            },
14246            window,
14247            cx,
14248        );
14249    });
14250    assert!(!(*is_still_following.borrow()));
14251}
14252
14253#[gpui::test]
14254async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14255    init_test(cx, |_| {});
14256
14257    let fs = FakeFs::new(cx.executor());
14258    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14259    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14260    let pane = workspace
14261        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14262        .unwrap();
14263
14264    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14265
14266    let leader = pane.update_in(cx, |_, window, cx| {
14267        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14268        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14269    });
14270
14271    // Start following the editor when it has no excerpts.
14272    let mut state_message =
14273        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14274    let workspace_entity = workspace.root(cx).unwrap();
14275    let follower_1 = cx
14276        .update_window(*workspace.deref(), |_, window, cx| {
14277            Editor::from_state_proto(
14278                workspace_entity,
14279                ViewId {
14280                    creator: CollaboratorId::PeerId(PeerId::default()),
14281                    id: 0,
14282                },
14283                &mut state_message,
14284                window,
14285                cx,
14286            )
14287        })
14288        .unwrap()
14289        .unwrap()
14290        .await
14291        .unwrap();
14292
14293    let update_message = Rc::new(RefCell::new(None));
14294    follower_1.update_in(cx, {
14295        let update = update_message.clone();
14296        |_, window, cx| {
14297            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14298                leader.read(cx).add_event_to_update_proto(
14299                    event,
14300                    &mut update.borrow_mut(),
14301                    window,
14302                    cx,
14303                );
14304            })
14305            .detach();
14306        }
14307    });
14308
14309    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14310        (
14311            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14312            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14313        )
14314    });
14315
14316    // Insert some excerpts.
14317    leader.update(cx, |leader, cx| {
14318        leader.buffer.update(cx, |multibuffer, cx| {
14319            multibuffer.set_excerpts_for_path(
14320                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14321                buffer_1.clone(),
14322                vec![
14323                    Point::row_range(0..3),
14324                    Point::row_range(1..6),
14325                    Point::row_range(12..15),
14326                ],
14327                0,
14328                cx,
14329            );
14330            multibuffer.set_excerpts_for_path(
14331                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14332                buffer_2.clone(),
14333                vec![Point::row_range(0..6), Point::row_range(8..12)],
14334                0,
14335                cx,
14336            );
14337        });
14338    });
14339
14340    // Apply the update of adding the excerpts.
14341    follower_1
14342        .update_in(cx, |follower, window, cx| {
14343            follower.apply_update_proto(
14344                &project,
14345                update_message.borrow().clone().unwrap(),
14346                window,
14347                cx,
14348            )
14349        })
14350        .await
14351        .unwrap();
14352    assert_eq!(
14353        follower_1.update(cx, |editor, cx| editor.text(cx)),
14354        leader.update(cx, |editor, cx| editor.text(cx))
14355    );
14356    update_message.borrow_mut().take();
14357
14358    // Start following separately after it already has excerpts.
14359    let mut state_message =
14360        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14361    let workspace_entity = workspace.root(cx).unwrap();
14362    let follower_2 = cx
14363        .update_window(*workspace.deref(), |_, window, cx| {
14364            Editor::from_state_proto(
14365                workspace_entity,
14366                ViewId {
14367                    creator: CollaboratorId::PeerId(PeerId::default()),
14368                    id: 0,
14369                },
14370                &mut state_message,
14371                window,
14372                cx,
14373            )
14374        })
14375        .unwrap()
14376        .unwrap()
14377        .await
14378        .unwrap();
14379    assert_eq!(
14380        follower_2.update(cx, |editor, cx| editor.text(cx)),
14381        leader.update(cx, |editor, cx| editor.text(cx))
14382    );
14383
14384    // Remove some excerpts.
14385    leader.update(cx, |leader, cx| {
14386        leader.buffer.update(cx, |multibuffer, cx| {
14387            let excerpt_ids = multibuffer.excerpt_ids();
14388            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14389            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14390        });
14391    });
14392
14393    // Apply the update of removing the excerpts.
14394    follower_1
14395        .update_in(cx, |follower, window, cx| {
14396            follower.apply_update_proto(
14397                &project,
14398                update_message.borrow().clone().unwrap(),
14399                window,
14400                cx,
14401            )
14402        })
14403        .await
14404        .unwrap();
14405    follower_2
14406        .update_in(cx, |follower, window, cx| {
14407            follower.apply_update_proto(
14408                &project,
14409                update_message.borrow().clone().unwrap(),
14410                window,
14411                cx,
14412            )
14413        })
14414        .await
14415        .unwrap();
14416    update_message.borrow_mut().take();
14417    assert_eq!(
14418        follower_1.update(cx, |editor, cx| editor.text(cx)),
14419        leader.update(cx, |editor, cx| editor.text(cx))
14420    );
14421}
14422
14423#[gpui::test]
14424async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14425    init_test(cx, |_| {});
14426
14427    let mut cx = EditorTestContext::new(cx).await;
14428    let lsp_store =
14429        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14430
14431    cx.set_state(indoc! {"
14432        ˇfn func(abc def: i32) -> u32 {
14433        }
14434    "});
14435
14436    cx.update(|_, cx| {
14437        lsp_store.update(cx, |lsp_store, cx| {
14438            lsp_store
14439                .update_diagnostics(
14440                    LanguageServerId(0),
14441                    lsp::PublishDiagnosticsParams {
14442                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14443                        version: None,
14444                        diagnostics: vec![
14445                            lsp::Diagnostic {
14446                                range: lsp::Range::new(
14447                                    lsp::Position::new(0, 11),
14448                                    lsp::Position::new(0, 12),
14449                                ),
14450                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14451                                ..Default::default()
14452                            },
14453                            lsp::Diagnostic {
14454                                range: lsp::Range::new(
14455                                    lsp::Position::new(0, 12),
14456                                    lsp::Position::new(0, 15),
14457                                ),
14458                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14459                                ..Default::default()
14460                            },
14461                            lsp::Diagnostic {
14462                                range: lsp::Range::new(
14463                                    lsp::Position::new(0, 25),
14464                                    lsp::Position::new(0, 28),
14465                                ),
14466                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14467                                ..Default::default()
14468                            },
14469                        ],
14470                    },
14471                    None,
14472                    DiagnosticSourceKind::Pushed,
14473                    &[],
14474                    cx,
14475                )
14476                .unwrap()
14477        });
14478    });
14479
14480    executor.run_until_parked();
14481
14482    cx.update_editor(|editor, window, cx| {
14483        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14484    });
14485
14486    cx.assert_editor_state(indoc! {"
14487        fn func(abc def: i32) -> ˇu32 {
14488        }
14489    "});
14490
14491    cx.update_editor(|editor, window, cx| {
14492        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14493    });
14494
14495    cx.assert_editor_state(indoc! {"
14496        fn func(abc ˇdef: i32) -> u32 {
14497        }
14498    "});
14499
14500    cx.update_editor(|editor, window, cx| {
14501        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14502    });
14503
14504    cx.assert_editor_state(indoc! {"
14505        fn func(abcˇ def: i32) -> u32 {
14506        }
14507    "});
14508
14509    cx.update_editor(|editor, window, cx| {
14510        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14511    });
14512
14513    cx.assert_editor_state(indoc! {"
14514        fn func(abc def: i32) -> ˇu32 {
14515        }
14516    "});
14517}
14518
14519#[gpui::test]
14520async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14521    init_test(cx, |_| {});
14522
14523    let mut cx = EditorTestContext::new(cx).await;
14524
14525    let diff_base = r#"
14526        use some::mod;
14527
14528        const A: u32 = 42;
14529
14530        fn main() {
14531            println!("hello");
14532
14533            println!("world");
14534        }
14535        "#
14536    .unindent();
14537
14538    // Edits are modified, removed, modified, added
14539    cx.set_state(
14540        &r#"
14541        use some::modified;
14542
14543        ˇ
14544        fn main() {
14545            println!("hello there");
14546
14547            println!("around the");
14548            println!("world");
14549        }
14550        "#
14551        .unindent(),
14552    );
14553
14554    cx.set_head_text(&diff_base);
14555    executor.run_until_parked();
14556
14557    cx.update_editor(|editor, window, cx| {
14558        //Wrap around the bottom of the buffer
14559        for _ in 0..3 {
14560            editor.go_to_next_hunk(&GoToHunk, window, cx);
14561        }
14562    });
14563
14564    cx.assert_editor_state(
14565        &r#"
14566        ˇuse some::modified;
14567
14568
14569        fn main() {
14570            println!("hello there");
14571
14572            println!("around the");
14573            println!("world");
14574        }
14575        "#
14576        .unindent(),
14577    );
14578
14579    cx.update_editor(|editor, window, cx| {
14580        //Wrap around the top of the buffer
14581        for _ in 0..2 {
14582            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14583        }
14584    });
14585
14586    cx.assert_editor_state(
14587        &r#"
14588        use some::modified;
14589
14590
14591        fn main() {
14592        ˇ    println!("hello there");
14593
14594            println!("around the");
14595            println!("world");
14596        }
14597        "#
14598        .unindent(),
14599    );
14600
14601    cx.update_editor(|editor, window, cx| {
14602        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14603    });
14604
14605    cx.assert_editor_state(
14606        &r#"
14607        use some::modified;
14608
14609        ˇ
14610        fn main() {
14611            println!("hello there");
14612
14613            println!("around the");
14614            println!("world");
14615        }
14616        "#
14617        .unindent(),
14618    );
14619
14620    cx.update_editor(|editor, window, cx| {
14621        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14622    });
14623
14624    cx.assert_editor_state(
14625        &r#"
14626        ˇuse some::modified;
14627
14628
14629        fn main() {
14630            println!("hello there");
14631
14632            println!("around the");
14633            println!("world");
14634        }
14635        "#
14636        .unindent(),
14637    );
14638
14639    cx.update_editor(|editor, window, cx| {
14640        for _ in 0..2 {
14641            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14642        }
14643    });
14644
14645    cx.assert_editor_state(
14646        &r#"
14647        use some::modified;
14648
14649
14650        fn main() {
14651        ˇ    println!("hello there");
14652
14653            println!("around the");
14654            println!("world");
14655        }
14656        "#
14657        .unindent(),
14658    );
14659
14660    cx.update_editor(|editor, window, cx| {
14661        editor.fold(&Fold, window, cx);
14662    });
14663
14664    cx.update_editor(|editor, window, cx| {
14665        editor.go_to_next_hunk(&GoToHunk, window, cx);
14666    });
14667
14668    cx.assert_editor_state(
14669        &r#"
14670        ˇuse some::modified;
14671
14672
14673        fn main() {
14674            println!("hello there");
14675
14676            println!("around the");
14677            println!("world");
14678        }
14679        "#
14680        .unindent(),
14681    );
14682}
14683
14684#[test]
14685fn test_split_words() {
14686    fn split(text: &str) -> Vec<&str> {
14687        split_words(text).collect()
14688    }
14689
14690    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14691    assert_eq!(split("hello_world"), &["hello_", "world"]);
14692    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14693    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14694    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14695    assert_eq!(split("helloworld"), &["helloworld"]);
14696
14697    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14698}
14699
14700#[gpui::test]
14701async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14702    init_test(cx, |_| {});
14703
14704    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14705    let mut assert = |before, after| {
14706        let _state_context = cx.set_state(before);
14707        cx.run_until_parked();
14708        cx.update_editor(|editor, window, cx| {
14709            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14710        });
14711        cx.run_until_parked();
14712        cx.assert_editor_state(after);
14713    };
14714
14715    // Outside bracket jumps to outside of matching bracket
14716    assert("console.logˇ(var);", "console.log(var)ˇ;");
14717    assert("console.log(var)ˇ;", "console.logˇ(var);");
14718
14719    // Inside bracket jumps to inside of matching bracket
14720    assert("console.log(ˇvar);", "console.log(varˇ);");
14721    assert("console.log(varˇ);", "console.log(ˇvar);");
14722
14723    // When outside a bracket and inside, favor jumping to the inside bracket
14724    assert(
14725        "console.log('foo', [1, 2, 3]ˇ);",
14726        "console.log(ˇ'foo', [1, 2, 3]);",
14727    );
14728    assert(
14729        "console.log(ˇ'foo', [1, 2, 3]);",
14730        "console.log('foo', [1, 2, 3]ˇ);",
14731    );
14732
14733    // Bias forward if two options are equally likely
14734    assert(
14735        "let result = curried_fun()ˇ();",
14736        "let result = curried_fun()()ˇ;",
14737    );
14738
14739    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14740    assert(
14741        indoc! {"
14742            function test() {
14743                console.log('test')ˇ
14744            }"},
14745        indoc! {"
14746            function test() {
14747                console.logˇ('test')
14748            }"},
14749    );
14750}
14751
14752#[gpui::test]
14753async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14754    init_test(cx, |_| {});
14755
14756    let fs = FakeFs::new(cx.executor());
14757    fs.insert_tree(
14758        path!("/a"),
14759        json!({
14760            "main.rs": "fn main() { let a = 5; }",
14761            "other.rs": "// Test file",
14762        }),
14763    )
14764    .await;
14765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14766
14767    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14768    language_registry.add(Arc::new(Language::new(
14769        LanguageConfig {
14770            name: "Rust".into(),
14771            matcher: LanguageMatcher {
14772                path_suffixes: vec!["rs".to_string()],
14773                ..Default::default()
14774            },
14775            brackets: BracketPairConfig {
14776                pairs: vec![BracketPair {
14777                    start: "{".to_string(),
14778                    end: "}".to_string(),
14779                    close: true,
14780                    surround: true,
14781                    newline: true,
14782                }],
14783                disabled_scopes_by_bracket_ix: Vec::new(),
14784            },
14785            ..Default::default()
14786        },
14787        Some(tree_sitter_rust::LANGUAGE.into()),
14788    )));
14789    let mut fake_servers = language_registry.register_fake_lsp(
14790        "Rust",
14791        FakeLspAdapter {
14792            capabilities: lsp::ServerCapabilities {
14793                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14794                    first_trigger_character: "{".to_string(),
14795                    more_trigger_character: None,
14796                }),
14797                ..Default::default()
14798            },
14799            ..Default::default()
14800        },
14801    );
14802
14803    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14804
14805    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14806
14807    let worktree_id = workspace
14808        .update(cx, |workspace, _, cx| {
14809            workspace.project().update(cx, |project, cx| {
14810                project.worktrees(cx).next().unwrap().read(cx).id()
14811            })
14812        })
14813        .unwrap();
14814
14815    let buffer = project
14816        .update(cx, |project, cx| {
14817            project.open_local_buffer(path!("/a/main.rs"), cx)
14818        })
14819        .await
14820        .unwrap();
14821    let editor_handle = workspace
14822        .update(cx, |workspace, window, cx| {
14823            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14824        })
14825        .unwrap()
14826        .await
14827        .unwrap()
14828        .downcast::<Editor>()
14829        .unwrap();
14830
14831    cx.executor().start_waiting();
14832    let fake_server = fake_servers.next().await.unwrap();
14833
14834    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14835        |params, _| async move {
14836            assert_eq!(
14837                params.text_document_position.text_document.uri,
14838                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14839            );
14840            assert_eq!(
14841                params.text_document_position.position,
14842                lsp::Position::new(0, 21),
14843            );
14844
14845            Ok(Some(vec![lsp::TextEdit {
14846                new_text: "]".to_string(),
14847                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14848            }]))
14849        },
14850    );
14851
14852    editor_handle.update_in(cx, |editor, window, cx| {
14853        window.focus(&editor.focus_handle(cx));
14854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14855            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14856        });
14857        editor.handle_input("{", window, cx);
14858    });
14859
14860    cx.executor().run_until_parked();
14861
14862    buffer.update(cx, |buffer, _| {
14863        assert_eq!(
14864            buffer.text(),
14865            "fn main() { let a = {5}; }",
14866            "No extra braces from on type formatting should appear in the buffer"
14867        )
14868    });
14869}
14870
14871#[gpui::test(iterations = 20, seeds(31))]
14872async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14873    init_test(cx, |_| {});
14874
14875    let mut cx = EditorLspTestContext::new_rust(
14876        lsp::ServerCapabilities {
14877            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14878                first_trigger_character: ".".to_string(),
14879                more_trigger_character: None,
14880            }),
14881            ..Default::default()
14882        },
14883        cx,
14884    )
14885    .await;
14886
14887    cx.update_buffer(|buffer, _| {
14888        // This causes autoindent to be async.
14889        buffer.set_sync_parse_timeout(Duration::ZERO)
14890    });
14891
14892    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14893    cx.simulate_keystroke("\n");
14894    cx.run_until_parked();
14895
14896    let buffer_cloned =
14897        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14898    let mut request =
14899        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14900            let buffer_cloned = buffer_cloned.clone();
14901            async move {
14902                buffer_cloned.update(&mut cx, |buffer, _| {
14903                    assert_eq!(
14904                        buffer.text(),
14905                        "fn c() {\n    d()\n        .\n}\n",
14906                        "OnTypeFormatting should triggered after autoindent applied"
14907                    )
14908                })?;
14909
14910                Ok(Some(vec![]))
14911            }
14912        });
14913
14914    cx.simulate_keystroke(".");
14915    cx.run_until_parked();
14916
14917    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14918    assert!(request.next().await.is_some());
14919    request.close();
14920    assert!(request.next().await.is_none());
14921}
14922
14923#[gpui::test]
14924async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14925    init_test(cx, |_| {});
14926
14927    let fs = FakeFs::new(cx.executor());
14928    fs.insert_tree(
14929        path!("/a"),
14930        json!({
14931            "main.rs": "fn main() { let a = 5; }",
14932            "other.rs": "// Test file",
14933        }),
14934    )
14935    .await;
14936
14937    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14938
14939    let server_restarts = Arc::new(AtomicUsize::new(0));
14940    let closure_restarts = Arc::clone(&server_restarts);
14941    let language_server_name = "test language server";
14942    let language_name: LanguageName = "Rust".into();
14943
14944    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14945    language_registry.add(Arc::new(Language::new(
14946        LanguageConfig {
14947            name: language_name.clone(),
14948            matcher: LanguageMatcher {
14949                path_suffixes: vec!["rs".to_string()],
14950                ..Default::default()
14951            },
14952            ..Default::default()
14953        },
14954        Some(tree_sitter_rust::LANGUAGE.into()),
14955    )));
14956    let mut fake_servers = language_registry.register_fake_lsp(
14957        "Rust",
14958        FakeLspAdapter {
14959            name: language_server_name,
14960            initialization_options: Some(json!({
14961                "testOptionValue": true
14962            })),
14963            initializer: Some(Box::new(move |fake_server| {
14964                let task_restarts = Arc::clone(&closure_restarts);
14965                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14966                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14967                    futures::future::ready(Ok(()))
14968                });
14969            })),
14970            ..Default::default()
14971        },
14972    );
14973
14974    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14975    let _buffer = project
14976        .update(cx, |project, cx| {
14977            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14978        })
14979        .await
14980        .unwrap();
14981    let _fake_server = fake_servers.next().await.unwrap();
14982    update_test_language_settings(cx, |language_settings| {
14983        language_settings.languages.0.insert(
14984            language_name.clone(),
14985            LanguageSettingsContent {
14986                tab_size: NonZeroU32::new(8),
14987                ..Default::default()
14988            },
14989        );
14990    });
14991    cx.executor().run_until_parked();
14992    assert_eq!(
14993        server_restarts.load(atomic::Ordering::Acquire),
14994        0,
14995        "Should not restart LSP server on an unrelated change"
14996    );
14997
14998    update_test_project_settings(cx, |project_settings| {
14999        project_settings.lsp.insert(
15000            "Some other server name".into(),
15001            LspSettings {
15002                binary: None,
15003                settings: None,
15004                initialization_options: Some(json!({
15005                    "some other init value": false
15006                })),
15007                enable_lsp_tasks: false,
15008            },
15009        );
15010    });
15011    cx.executor().run_until_parked();
15012    assert_eq!(
15013        server_restarts.load(atomic::Ordering::Acquire),
15014        0,
15015        "Should not restart LSP server on an unrelated LSP settings change"
15016    );
15017
15018    update_test_project_settings(cx, |project_settings| {
15019        project_settings.lsp.insert(
15020            language_server_name.into(),
15021            LspSettings {
15022                binary: None,
15023                settings: None,
15024                initialization_options: Some(json!({
15025                    "anotherInitValue": false
15026                })),
15027                enable_lsp_tasks: false,
15028            },
15029        );
15030    });
15031    cx.executor().run_until_parked();
15032    assert_eq!(
15033        server_restarts.load(atomic::Ordering::Acquire),
15034        1,
15035        "Should restart LSP server on a related LSP settings change"
15036    );
15037
15038    update_test_project_settings(cx, |project_settings| {
15039        project_settings.lsp.insert(
15040            language_server_name.into(),
15041            LspSettings {
15042                binary: None,
15043                settings: None,
15044                initialization_options: Some(json!({
15045                    "anotherInitValue": false
15046                })),
15047                enable_lsp_tasks: false,
15048            },
15049        );
15050    });
15051    cx.executor().run_until_parked();
15052    assert_eq!(
15053        server_restarts.load(atomic::Ordering::Acquire),
15054        1,
15055        "Should not restart LSP server on a related LSP settings change that is the same"
15056    );
15057
15058    update_test_project_settings(cx, |project_settings| {
15059        project_settings.lsp.insert(
15060            language_server_name.into(),
15061            LspSettings {
15062                binary: None,
15063                settings: None,
15064                initialization_options: None,
15065                enable_lsp_tasks: false,
15066            },
15067        );
15068    });
15069    cx.executor().run_until_parked();
15070    assert_eq!(
15071        server_restarts.load(atomic::Ordering::Acquire),
15072        2,
15073        "Should restart LSP server on another related LSP settings change"
15074    );
15075}
15076
15077#[gpui::test]
15078async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15079    init_test(cx, |_| {});
15080
15081    let mut cx = EditorLspTestContext::new_rust(
15082        lsp::ServerCapabilities {
15083            completion_provider: Some(lsp::CompletionOptions {
15084                trigger_characters: Some(vec![".".to_string()]),
15085                resolve_provider: Some(true),
15086                ..Default::default()
15087            }),
15088            ..Default::default()
15089        },
15090        cx,
15091    )
15092    .await;
15093
15094    cx.set_state("fn main() { let a = 2ˇ; }");
15095    cx.simulate_keystroke(".");
15096    let completion_item = lsp::CompletionItem {
15097        label: "some".into(),
15098        kind: Some(lsp::CompletionItemKind::SNIPPET),
15099        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15100        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15101            kind: lsp::MarkupKind::Markdown,
15102            value: "```rust\nSome(2)\n```".to_string(),
15103        })),
15104        deprecated: Some(false),
15105        sort_text: Some("fffffff2".to_string()),
15106        filter_text: Some("some".to_string()),
15107        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15108        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15109            range: lsp::Range {
15110                start: lsp::Position {
15111                    line: 0,
15112                    character: 22,
15113                },
15114                end: lsp::Position {
15115                    line: 0,
15116                    character: 22,
15117                },
15118            },
15119            new_text: "Some(2)".to_string(),
15120        })),
15121        additional_text_edits: Some(vec![lsp::TextEdit {
15122            range: lsp::Range {
15123                start: lsp::Position {
15124                    line: 0,
15125                    character: 20,
15126                },
15127                end: lsp::Position {
15128                    line: 0,
15129                    character: 22,
15130                },
15131            },
15132            new_text: "".to_string(),
15133        }]),
15134        ..Default::default()
15135    };
15136
15137    let closure_completion_item = completion_item.clone();
15138    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15139        let task_completion_item = closure_completion_item.clone();
15140        async move {
15141            Ok(Some(lsp::CompletionResponse::Array(vec![
15142                task_completion_item,
15143            ])))
15144        }
15145    });
15146
15147    request.next().await;
15148
15149    cx.condition(|editor, _| editor.context_menu_visible())
15150        .await;
15151    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15152        editor
15153            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15154            .unwrap()
15155    });
15156    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15157
15158    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15159        let task_completion_item = completion_item.clone();
15160        async move { Ok(task_completion_item) }
15161    })
15162    .next()
15163    .await
15164    .unwrap();
15165    apply_additional_edits.await.unwrap();
15166    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15167}
15168
15169#[gpui::test]
15170async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15171    init_test(cx, |_| {});
15172
15173    let mut cx = EditorLspTestContext::new_rust(
15174        lsp::ServerCapabilities {
15175            completion_provider: Some(lsp::CompletionOptions {
15176                trigger_characters: Some(vec![".".to_string()]),
15177                resolve_provider: Some(true),
15178                ..Default::default()
15179            }),
15180            ..Default::default()
15181        },
15182        cx,
15183    )
15184    .await;
15185
15186    cx.set_state("fn main() { let a = 2ˇ; }");
15187    cx.simulate_keystroke(".");
15188
15189    let item1 = lsp::CompletionItem {
15190        label: "method id()".to_string(),
15191        filter_text: Some("id".to_string()),
15192        detail: None,
15193        documentation: None,
15194        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15195            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15196            new_text: ".id".to_string(),
15197        })),
15198        ..lsp::CompletionItem::default()
15199    };
15200
15201    let item2 = lsp::CompletionItem {
15202        label: "other".to_string(),
15203        filter_text: Some("other".to_string()),
15204        detail: None,
15205        documentation: None,
15206        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15207            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15208            new_text: ".other".to_string(),
15209        })),
15210        ..lsp::CompletionItem::default()
15211    };
15212
15213    let item1 = item1.clone();
15214    cx.set_request_handler::<lsp::request::Completion, _, _>({
15215        let item1 = item1.clone();
15216        move |_, _, _| {
15217            let item1 = item1.clone();
15218            let item2 = item2.clone();
15219            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15220        }
15221    })
15222    .next()
15223    .await;
15224
15225    cx.condition(|editor, _| editor.context_menu_visible())
15226        .await;
15227    cx.update_editor(|editor, _, _| {
15228        let context_menu = editor.context_menu.borrow_mut();
15229        let context_menu = context_menu
15230            .as_ref()
15231            .expect("Should have the context menu deployed");
15232        match context_menu {
15233            CodeContextMenu::Completions(completions_menu) => {
15234                let completions = completions_menu.completions.borrow_mut();
15235                assert_eq!(
15236                    completions
15237                        .iter()
15238                        .map(|completion| &completion.label.text)
15239                        .collect::<Vec<_>>(),
15240                    vec!["method id()", "other"]
15241                )
15242            }
15243            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15244        }
15245    });
15246
15247    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15248        let item1 = item1.clone();
15249        move |_, item_to_resolve, _| {
15250            let item1 = item1.clone();
15251            async move {
15252                if item1 == item_to_resolve {
15253                    Ok(lsp::CompletionItem {
15254                        label: "method id()".to_string(),
15255                        filter_text: Some("id".to_string()),
15256                        detail: Some("Now resolved!".to_string()),
15257                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15258                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15259                            range: lsp::Range::new(
15260                                lsp::Position::new(0, 22),
15261                                lsp::Position::new(0, 22),
15262                            ),
15263                            new_text: ".id".to_string(),
15264                        })),
15265                        ..lsp::CompletionItem::default()
15266                    })
15267                } else {
15268                    Ok(item_to_resolve)
15269                }
15270            }
15271        }
15272    })
15273    .next()
15274    .await
15275    .unwrap();
15276    cx.run_until_parked();
15277
15278    cx.update_editor(|editor, window, cx| {
15279        editor.context_menu_next(&Default::default(), window, cx);
15280    });
15281
15282    cx.update_editor(|editor, _, _| {
15283        let context_menu = editor.context_menu.borrow_mut();
15284        let context_menu = context_menu
15285            .as_ref()
15286            .expect("Should have the context menu deployed");
15287        match context_menu {
15288            CodeContextMenu::Completions(completions_menu) => {
15289                let completions = completions_menu.completions.borrow_mut();
15290                assert_eq!(
15291                    completions
15292                        .iter()
15293                        .map(|completion| &completion.label.text)
15294                        .collect::<Vec<_>>(),
15295                    vec!["method id() Now resolved!", "other"],
15296                    "Should update first completion label, but not second as the filter text did not match."
15297                );
15298            }
15299            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15300        }
15301    });
15302}
15303
15304#[gpui::test]
15305async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15306    init_test(cx, |_| {});
15307    let mut cx = EditorLspTestContext::new_rust(
15308        lsp::ServerCapabilities {
15309            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15310            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15311            completion_provider: Some(lsp::CompletionOptions {
15312                resolve_provider: Some(true),
15313                ..Default::default()
15314            }),
15315            ..Default::default()
15316        },
15317        cx,
15318    )
15319    .await;
15320    cx.set_state(indoc! {"
15321        struct TestStruct {
15322            field: i32
15323        }
15324
15325        fn mainˇ() {
15326            let unused_var = 42;
15327            let test_struct = TestStruct { field: 42 };
15328        }
15329    "});
15330    let symbol_range = cx.lsp_range(indoc! {"
15331        struct TestStruct {
15332            field: i32
15333        }
15334
15335        «fn main»() {
15336            let unused_var = 42;
15337            let test_struct = TestStruct { field: 42 };
15338        }
15339    "});
15340    let mut hover_requests =
15341        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15342            Ok(Some(lsp::Hover {
15343                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15344                    kind: lsp::MarkupKind::Markdown,
15345                    value: "Function documentation".to_string(),
15346                }),
15347                range: Some(symbol_range),
15348            }))
15349        });
15350
15351    // Case 1: Test that code action menu hide hover popover
15352    cx.dispatch_action(Hover);
15353    hover_requests.next().await;
15354    cx.condition(|editor, _| editor.hover_state.visible()).await;
15355    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15356        move |_, _, _| async move {
15357            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15358                lsp::CodeAction {
15359                    title: "Remove unused variable".to_string(),
15360                    kind: Some(CodeActionKind::QUICKFIX),
15361                    edit: Some(lsp::WorkspaceEdit {
15362                        changes: Some(
15363                            [(
15364                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15365                                vec![lsp::TextEdit {
15366                                    range: lsp::Range::new(
15367                                        lsp::Position::new(5, 4),
15368                                        lsp::Position::new(5, 27),
15369                                    ),
15370                                    new_text: "".to_string(),
15371                                }],
15372                            )]
15373                            .into_iter()
15374                            .collect(),
15375                        ),
15376                        ..Default::default()
15377                    }),
15378                    ..Default::default()
15379                },
15380            )]))
15381        },
15382    );
15383    cx.update_editor(|editor, window, cx| {
15384        editor.toggle_code_actions(
15385            &ToggleCodeActions {
15386                deployed_from: None,
15387                quick_launch: false,
15388            },
15389            window,
15390            cx,
15391        );
15392    });
15393    code_action_requests.next().await;
15394    cx.run_until_parked();
15395    cx.condition(|editor, _| editor.context_menu_visible())
15396        .await;
15397    cx.update_editor(|editor, _, _| {
15398        assert!(
15399            !editor.hover_state.visible(),
15400            "Hover popover should be hidden when code action menu is shown"
15401        );
15402        // Hide code actions
15403        editor.context_menu.take();
15404    });
15405
15406    // Case 2: Test that code completions hide hover popover
15407    cx.dispatch_action(Hover);
15408    hover_requests.next().await;
15409    cx.condition(|editor, _| editor.hover_state.visible()).await;
15410    let counter = Arc::new(AtomicUsize::new(0));
15411    let mut completion_requests =
15412        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15413            let counter = counter.clone();
15414            async move {
15415                counter.fetch_add(1, atomic::Ordering::Release);
15416                Ok(Some(lsp::CompletionResponse::Array(vec![
15417                    lsp::CompletionItem {
15418                        label: "main".into(),
15419                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15420                        detail: Some("() -> ()".to_string()),
15421                        ..Default::default()
15422                    },
15423                    lsp::CompletionItem {
15424                        label: "TestStruct".into(),
15425                        kind: Some(lsp::CompletionItemKind::STRUCT),
15426                        detail: Some("struct TestStruct".to_string()),
15427                        ..Default::default()
15428                    },
15429                ])))
15430            }
15431        });
15432    cx.update_editor(|editor, window, cx| {
15433        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15434    });
15435    completion_requests.next().await;
15436    cx.condition(|editor, _| editor.context_menu_visible())
15437        .await;
15438    cx.update_editor(|editor, _, _| {
15439        assert!(
15440            !editor.hover_state.visible(),
15441            "Hover popover should be hidden when completion menu is shown"
15442        );
15443    });
15444}
15445
15446#[gpui::test]
15447async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15448    init_test(cx, |_| {});
15449
15450    let mut cx = EditorLspTestContext::new_rust(
15451        lsp::ServerCapabilities {
15452            completion_provider: Some(lsp::CompletionOptions {
15453                trigger_characters: Some(vec![".".to_string()]),
15454                resolve_provider: Some(true),
15455                ..Default::default()
15456            }),
15457            ..Default::default()
15458        },
15459        cx,
15460    )
15461    .await;
15462
15463    cx.set_state("fn main() { let a = 2ˇ; }");
15464    cx.simulate_keystroke(".");
15465
15466    let unresolved_item_1 = lsp::CompletionItem {
15467        label: "id".to_string(),
15468        filter_text: Some("id".to_string()),
15469        detail: None,
15470        documentation: None,
15471        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15472            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15473            new_text: ".id".to_string(),
15474        })),
15475        ..lsp::CompletionItem::default()
15476    };
15477    let resolved_item_1 = lsp::CompletionItem {
15478        additional_text_edits: Some(vec![lsp::TextEdit {
15479            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15480            new_text: "!!".to_string(),
15481        }]),
15482        ..unresolved_item_1.clone()
15483    };
15484    let unresolved_item_2 = lsp::CompletionItem {
15485        label: "other".to_string(),
15486        filter_text: Some("other".to_string()),
15487        detail: None,
15488        documentation: None,
15489        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15490            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15491            new_text: ".other".to_string(),
15492        })),
15493        ..lsp::CompletionItem::default()
15494    };
15495    let resolved_item_2 = lsp::CompletionItem {
15496        additional_text_edits: Some(vec![lsp::TextEdit {
15497            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15498            new_text: "??".to_string(),
15499        }]),
15500        ..unresolved_item_2.clone()
15501    };
15502
15503    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15504    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15505    cx.lsp
15506        .server
15507        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15508            let unresolved_item_1 = unresolved_item_1.clone();
15509            let resolved_item_1 = resolved_item_1.clone();
15510            let unresolved_item_2 = unresolved_item_2.clone();
15511            let resolved_item_2 = resolved_item_2.clone();
15512            let resolve_requests_1 = resolve_requests_1.clone();
15513            let resolve_requests_2 = resolve_requests_2.clone();
15514            move |unresolved_request, _| {
15515                let unresolved_item_1 = unresolved_item_1.clone();
15516                let resolved_item_1 = resolved_item_1.clone();
15517                let unresolved_item_2 = unresolved_item_2.clone();
15518                let resolved_item_2 = resolved_item_2.clone();
15519                let resolve_requests_1 = resolve_requests_1.clone();
15520                let resolve_requests_2 = resolve_requests_2.clone();
15521                async move {
15522                    if unresolved_request == unresolved_item_1 {
15523                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15524                        Ok(resolved_item_1.clone())
15525                    } else if unresolved_request == unresolved_item_2 {
15526                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15527                        Ok(resolved_item_2.clone())
15528                    } else {
15529                        panic!("Unexpected completion item {unresolved_request:?}")
15530                    }
15531                }
15532            }
15533        })
15534        .detach();
15535
15536    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15537        let unresolved_item_1 = unresolved_item_1.clone();
15538        let unresolved_item_2 = unresolved_item_2.clone();
15539        async move {
15540            Ok(Some(lsp::CompletionResponse::Array(vec![
15541                unresolved_item_1,
15542                unresolved_item_2,
15543            ])))
15544        }
15545    })
15546    .next()
15547    .await;
15548
15549    cx.condition(|editor, _| editor.context_menu_visible())
15550        .await;
15551    cx.update_editor(|editor, _, _| {
15552        let context_menu = editor.context_menu.borrow_mut();
15553        let context_menu = context_menu
15554            .as_ref()
15555            .expect("Should have the context menu deployed");
15556        match context_menu {
15557            CodeContextMenu::Completions(completions_menu) => {
15558                let completions = completions_menu.completions.borrow_mut();
15559                assert_eq!(
15560                    completions
15561                        .iter()
15562                        .map(|completion| &completion.label.text)
15563                        .collect::<Vec<_>>(),
15564                    vec!["id", "other"]
15565                )
15566            }
15567            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15568        }
15569    });
15570    cx.run_until_parked();
15571
15572    cx.update_editor(|editor, window, cx| {
15573        editor.context_menu_next(&ContextMenuNext, window, cx);
15574    });
15575    cx.run_until_parked();
15576    cx.update_editor(|editor, window, cx| {
15577        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15578    });
15579    cx.run_until_parked();
15580    cx.update_editor(|editor, window, cx| {
15581        editor.context_menu_next(&ContextMenuNext, window, cx);
15582    });
15583    cx.run_until_parked();
15584    cx.update_editor(|editor, window, cx| {
15585        editor
15586            .compose_completion(&ComposeCompletion::default(), window, cx)
15587            .expect("No task returned")
15588    })
15589    .await
15590    .expect("Completion failed");
15591    cx.run_until_parked();
15592
15593    cx.update_editor(|editor, _, cx| {
15594        assert_eq!(
15595            resolve_requests_1.load(atomic::Ordering::Acquire),
15596            1,
15597            "Should always resolve once despite multiple selections"
15598        );
15599        assert_eq!(
15600            resolve_requests_2.load(atomic::Ordering::Acquire),
15601            1,
15602            "Should always resolve once after multiple selections and applying the completion"
15603        );
15604        assert_eq!(
15605            editor.text(cx),
15606            "fn main() { let a = ??.other; }",
15607            "Should use resolved data when applying the completion"
15608        );
15609    });
15610}
15611
15612#[gpui::test]
15613async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15614    init_test(cx, |_| {});
15615
15616    let item_0 = lsp::CompletionItem {
15617        label: "abs".into(),
15618        insert_text: Some("abs".into()),
15619        data: Some(json!({ "very": "special"})),
15620        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15621        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15622            lsp::InsertReplaceEdit {
15623                new_text: "abs".to_string(),
15624                insert: lsp::Range::default(),
15625                replace: lsp::Range::default(),
15626            },
15627        )),
15628        ..lsp::CompletionItem::default()
15629    };
15630    let items = iter::once(item_0.clone())
15631        .chain((11..51).map(|i| lsp::CompletionItem {
15632            label: format!("item_{}", i),
15633            insert_text: Some(format!("item_{}", i)),
15634            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15635            ..lsp::CompletionItem::default()
15636        }))
15637        .collect::<Vec<_>>();
15638
15639    let default_commit_characters = vec!["?".to_string()];
15640    let default_data = json!({ "default": "data"});
15641    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15642    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15643    let default_edit_range = lsp::Range {
15644        start: lsp::Position {
15645            line: 0,
15646            character: 5,
15647        },
15648        end: lsp::Position {
15649            line: 0,
15650            character: 5,
15651        },
15652    };
15653
15654    let mut cx = EditorLspTestContext::new_rust(
15655        lsp::ServerCapabilities {
15656            completion_provider: Some(lsp::CompletionOptions {
15657                trigger_characters: Some(vec![".".to_string()]),
15658                resolve_provider: Some(true),
15659                ..Default::default()
15660            }),
15661            ..Default::default()
15662        },
15663        cx,
15664    )
15665    .await;
15666
15667    cx.set_state("fn main() { let a = 2ˇ; }");
15668    cx.simulate_keystroke(".");
15669
15670    let completion_data = default_data.clone();
15671    let completion_characters = default_commit_characters.clone();
15672    let completion_items = items.clone();
15673    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15674        let default_data = completion_data.clone();
15675        let default_commit_characters = completion_characters.clone();
15676        let items = completion_items.clone();
15677        async move {
15678            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15679                items,
15680                item_defaults: Some(lsp::CompletionListItemDefaults {
15681                    data: Some(default_data.clone()),
15682                    commit_characters: Some(default_commit_characters.clone()),
15683                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15684                        default_edit_range,
15685                    )),
15686                    insert_text_format: Some(default_insert_text_format),
15687                    insert_text_mode: Some(default_insert_text_mode),
15688                }),
15689                ..lsp::CompletionList::default()
15690            })))
15691        }
15692    })
15693    .next()
15694    .await;
15695
15696    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15697    cx.lsp
15698        .server
15699        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15700            let closure_resolved_items = resolved_items.clone();
15701            move |item_to_resolve, _| {
15702                let closure_resolved_items = closure_resolved_items.clone();
15703                async move {
15704                    closure_resolved_items.lock().push(item_to_resolve.clone());
15705                    Ok(item_to_resolve)
15706                }
15707            }
15708        })
15709        .detach();
15710
15711    cx.condition(|editor, _| editor.context_menu_visible())
15712        .await;
15713    cx.run_until_parked();
15714    cx.update_editor(|editor, _, _| {
15715        let menu = editor.context_menu.borrow_mut();
15716        match menu.as_ref().expect("should have the completions menu") {
15717            CodeContextMenu::Completions(completions_menu) => {
15718                assert_eq!(
15719                    completions_menu
15720                        .entries
15721                        .borrow()
15722                        .iter()
15723                        .map(|mat| mat.string.clone())
15724                        .collect::<Vec<String>>(),
15725                    items
15726                        .iter()
15727                        .map(|completion| completion.label.clone())
15728                        .collect::<Vec<String>>()
15729                );
15730            }
15731            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15732        }
15733    });
15734    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15735    // with 4 from the end.
15736    assert_eq!(
15737        *resolved_items.lock(),
15738        [&items[0..16], &items[items.len() - 4..items.len()]]
15739            .concat()
15740            .iter()
15741            .cloned()
15742            .map(|mut item| {
15743                if item.data.is_none() {
15744                    item.data = Some(default_data.clone());
15745                }
15746                item
15747            })
15748            .collect::<Vec<lsp::CompletionItem>>(),
15749        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15750    );
15751    resolved_items.lock().clear();
15752
15753    cx.update_editor(|editor, window, cx| {
15754        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15755    });
15756    cx.run_until_parked();
15757    // Completions that have already been resolved are skipped.
15758    assert_eq!(
15759        *resolved_items.lock(),
15760        items[items.len() - 17..items.len() - 4]
15761            .iter()
15762            .cloned()
15763            .map(|mut item| {
15764                if item.data.is_none() {
15765                    item.data = Some(default_data.clone());
15766                }
15767                item
15768            })
15769            .collect::<Vec<lsp::CompletionItem>>()
15770    );
15771    resolved_items.lock().clear();
15772}
15773
15774#[gpui::test]
15775async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15776    init_test(cx, |_| {});
15777
15778    let mut cx = EditorLspTestContext::new(
15779        Language::new(
15780            LanguageConfig {
15781                matcher: LanguageMatcher {
15782                    path_suffixes: vec!["jsx".into()],
15783                    ..Default::default()
15784                },
15785                overrides: [(
15786                    "element".into(),
15787                    LanguageConfigOverride {
15788                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15789                        ..Default::default()
15790                    },
15791                )]
15792                .into_iter()
15793                .collect(),
15794                ..Default::default()
15795            },
15796            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15797        )
15798        .with_override_query("(jsx_self_closing_element) @element")
15799        .unwrap(),
15800        lsp::ServerCapabilities {
15801            completion_provider: Some(lsp::CompletionOptions {
15802                trigger_characters: Some(vec![":".to_string()]),
15803                ..Default::default()
15804            }),
15805            ..Default::default()
15806        },
15807        cx,
15808    )
15809    .await;
15810
15811    cx.lsp
15812        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15813            Ok(Some(lsp::CompletionResponse::Array(vec![
15814                lsp::CompletionItem {
15815                    label: "bg-blue".into(),
15816                    ..Default::default()
15817                },
15818                lsp::CompletionItem {
15819                    label: "bg-red".into(),
15820                    ..Default::default()
15821                },
15822                lsp::CompletionItem {
15823                    label: "bg-yellow".into(),
15824                    ..Default::default()
15825                },
15826            ])))
15827        });
15828
15829    cx.set_state(r#"<p class="bgˇ" />"#);
15830
15831    // Trigger completion when typing a dash, because the dash is an extra
15832    // word character in the 'element' scope, which contains the cursor.
15833    cx.simulate_keystroke("-");
15834    cx.executor().run_until_parked();
15835    cx.update_editor(|editor, _, _| {
15836        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15837        {
15838            assert_eq!(
15839                completion_menu_entries(&menu),
15840                &["bg-blue", "bg-red", "bg-yellow"]
15841            );
15842        } else {
15843            panic!("expected completion menu to be open");
15844        }
15845    });
15846
15847    cx.simulate_keystroke("l");
15848    cx.executor().run_until_parked();
15849    cx.update_editor(|editor, _, _| {
15850        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15851        {
15852            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15853        } else {
15854            panic!("expected completion menu to be open");
15855        }
15856    });
15857
15858    // When filtering completions, consider the character after the '-' to
15859    // be the start of a subword.
15860    cx.set_state(r#"<p class="yelˇ" />"#);
15861    cx.simulate_keystroke("l");
15862    cx.executor().run_until_parked();
15863    cx.update_editor(|editor, _, _| {
15864        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15865        {
15866            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15867        } else {
15868            panic!("expected completion menu to be open");
15869        }
15870    });
15871}
15872
15873fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15874    let entries = menu.entries.borrow();
15875    entries.iter().map(|mat| mat.string.clone()).collect()
15876}
15877
15878#[gpui::test]
15879async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15880    init_test(cx, |settings| {
15881        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
15882            Formatter::Prettier,
15883        ]))
15884    });
15885
15886    let fs = FakeFs::new(cx.executor());
15887    fs.insert_file(path!("/file.ts"), Default::default()).await;
15888
15889    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15890    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15891
15892    language_registry.add(Arc::new(Language::new(
15893        LanguageConfig {
15894            name: "TypeScript".into(),
15895            matcher: LanguageMatcher {
15896                path_suffixes: vec!["ts".to_string()],
15897                ..Default::default()
15898            },
15899            ..Default::default()
15900        },
15901        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15902    )));
15903    update_test_language_settings(cx, |settings| {
15904        settings.defaults.prettier = Some(PrettierSettings {
15905            allowed: true,
15906            ..PrettierSettings::default()
15907        });
15908    });
15909
15910    let test_plugin = "test_plugin";
15911    let _ = language_registry.register_fake_lsp(
15912        "TypeScript",
15913        FakeLspAdapter {
15914            prettier_plugins: vec![test_plugin],
15915            ..Default::default()
15916        },
15917    );
15918
15919    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15920    let buffer = project
15921        .update(cx, |project, cx| {
15922            project.open_local_buffer(path!("/file.ts"), cx)
15923        })
15924        .await
15925        .unwrap();
15926
15927    let buffer_text = "one\ntwo\nthree\n";
15928    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15929    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15930    editor.update_in(cx, |editor, window, cx| {
15931        editor.set_text(buffer_text, window, cx)
15932    });
15933
15934    editor
15935        .update_in(cx, |editor, window, cx| {
15936            editor.perform_format(
15937                project.clone(),
15938                FormatTrigger::Manual,
15939                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15940                window,
15941                cx,
15942            )
15943        })
15944        .unwrap()
15945        .await;
15946    assert_eq!(
15947        editor.update(cx, |editor, cx| editor.text(cx)),
15948        buffer_text.to_string() + prettier_format_suffix,
15949        "Test prettier formatting was not applied to the original buffer text",
15950    );
15951
15952    update_test_language_settings(cx, |settings| {
15953        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15954    });
15955    let format = editor.update_in(cx, |editor, window, cx| {
15956        editor.perform_format(
15957            project.clone(),
15958            FormatTrigger::Manual,
15959            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15960            window,
15961            cx,
15962        )
15963    });
15964    format.await.unwrap();
15965    assert_eq!(
15966        editor.update(cx, |editor, cx| editor.text(cx)),
15967        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15968        "Autoformatting (via test prettier) was not applied to the original buffer text",
15969    );
15970}
15971
15972#[gpui::test]
15973async fn test_addition_reverts(cx: &mut TestAppContext) {
15974    init_test(cx, |_| {});
15975    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15976    let base_text = indoc! {r#"
15977        struct Row;
15978        struct Row1;
15979        struct Row2;
15980
15981        struct Row4;
15982        struct Row5;
15983        struct Row6;
15984
15985        struct Row8;
15986        struct Row9;
15987        struct Row10;"#};
15988
15989    // When addition hunks are not adjacent to carets, no hunk revert is performed
15990    assert_hunk_revert(
15991        indoc! {r#"struct Row;
15992                   struct Row1;
15993                   struct Row1.1;
15994                   struct Row1.2;
15995                   struct Row2;ˇ
15996
15997                   struct Row4;
15998                   struct Row5;
15999                   struct Row6;
16000
16001                   struct Row8;
16002                   ˇstruct Row9;
16003                   struct Row9.1;
16004                   struct Row9.2;
16005                   struct Row9.3;
16006                   struct Row10;"#},
16007        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16008        indoc! {r#"struct Row;
16009                   struct Row1;
16010                   struct Row1.1;
16011                   struct Row1.2;
16012                   struct Row2;ˇ
16013
16014                   struct Row4;
16015                   struct Row5;
16016                   struct Row6;
16017
16018                   struct Row8;
16019                   ˇstruct Row9;
16020                   struct Row9.1;
16021                   struct Row9.2;
16022                   struct Row9.3;
16023                   struct Row10;"#},
16024        base_text,
16025        &mut cx,
16026    );
16027    // Same for selections
16028    assert_hunk_revert(
16029        indoc! {r#"struct Row;
16030                   struct Row1;
16031                   struct Row2;
16032                   struct Row2.1;
16033                   struct Row2.2;
16034                   «ˇ
16035                   struct Row4;
16036                   struct» Row5;
16037                   «struct Row6;
16038                   ˇ»
16039                   struct Row9.1;
16040                   struct Row9.2;
16041                   struct Row9.3;
16042                   struct Row8;
16043                   struct Row9;
16044                   struct Row10;"#},
16045        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16046        indoc! {r#"struct Row;
16047                   struct Row1;
16048                   struct Row2;
16049                   struct Row2.1;
16050                   struct Row2.2;
16051                   «ˇ
16052                   struct Row4;
16053                   struct» Row5;
16054                   «struct Row6;
16055                   ˇ»
16056                   struct Row9.1;
16057                   struct Row9.2;
16058                   struct Row9.3;
16059                   struct Row8;
16060                   struct Row9;
16061                   struct Row10;"#},
16062        base_text,
16063        &mut cx,
16064    );
16065
16066    // When carets and selections intersect the addition hunks, those are reverted.
16067    // Adjacent carets got merged.
16068    assert_hunk_revert(
16069        indoc! {r#"struct Row;
16070                   ˇ// something on the top
16071                   struct Row1;
16072                   struct Row2;
16073                   struct Roˇw3.1;
16074                   struct Row2.2;
16075                   struct Row2.3;ˇ
16076
16077                   struct Row4;
16078                   struct ˇRow5.1;
16079                   struct Row5.2;
16080                   struct «Rowˇ»5.3;
16081                   struct Row5;
16082                   struct Row6;
16083                   ˇ
16084                   struct Row9.1;
16085                   struct «Rowˇ»9.2;
16086                   struct «ˇRow»9.3;
16087                   struct Row8;
16088                   struct Row9;
16089                   «ˇ// something on bottom»
16090                   struct Row10;"#},
16091        vec![
16092            DiffHunkStatusKind::Added,
16093            DiffHunkStatusKind::Added,
16094            DiffHunkStatusKind::Added,
16095            DiffHunkStatusKind::Added,
16096            DiffHunkStatusKind::Added,
16097        ],
16098        indoc! {r#"struct Row;
16099                   ˇstruct Row1;
16100                   struct Row2;
16101                   ˇ
16102                   struct Row4;
16103                   ˇstruct Row5;
16104                   struct Row6;
16105                   ˇ
16106                   ˇstruct Row8;
16107                   struct Row9;
16108                   ˇstruct Row10;"#},
16109        base_text,
16110        &mut cx,
16111    );
16112}
16113
16114#[gpui::test]
16115async fn test_modification_reverts(cx: &mut TestAppContext) {
16116    init_test(cx, |_| {});
16117    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16118    let base_text = indoc! {r#"
16119        struct Row;
16120        struct Row1;
16121        struct Row2;
16122
16123        struct Row4;
16124        struct Row5;
16125        struct Row6;
16126
16127        struct Row8;
16128        struct Row9;
16129        struct Row10;"#};
16130
16131    // Modification hunks behave the same as the addition ones.
16132    assert_hunk_revert(
16133        indoc! {r#"struct Row;
16134                   struct Row1;
16135                   struct Row33;
16136                   ˇ
16137                   struct Row4;
16138                   struct Row5;
16139                   struct Row6;
16140                   ˇ
16141                   struct Row99;
16142                   struct Row9;
16143                   struct Row10;"#},
16144        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16145        indoc! {r#"struct Row;
16146                   struct Row1;
16147                   struct Row33;
16148                   ˇ
16149                   struct Row4;
16150                   struct Row5;
16151                   struct Row6;
16152                   ˇ
16153                   struct Row99;
16154                   struct Row9;
16155                   struct Row10;"#},
16156        base_text,
16157        &mut cx,
16158    );
16159    assert_hunk_revert(
16160        indoc! {r#"struct Row;
16161                   struct Row1;
16162                   struct Row33;
16163                   «ˇ
16164                   struct Row4;
16165                   struct» Row5;
16166                   «struct Row6;
16167                   ˇ»
16168                   struct Row99;
16169                   struct Row9;
16170                   struct Row10;"#},
16171        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16172        indoc! {r#"struct Row;
16173                   struct Row1;
16174                   struct Row33;
16175                   «ˇ
16176                   struct Row4;
16177                   struct» Row5;
16178                   «struct Row6;
16179                   ˇ»
16180                   struct Row99;
16181                   struct Row9;
16182                   struct Row10;"#},
16183        base_text,
16184        &mut cx,
16185    );
16186
16187    assert_hunk_revert(
16188        indoc! {r#"ˇstruct Row1.1;
16189                   struct Row1;
16190                   «ˇstr»uct Row22;
16191
16192                   struct ˇRow44;
16193                   struct Row5;
16194                   struct «Rˇ»ow66;ˇ
16195
16196                   «struˇ»ct Row88;
16197                   struct Row9;
16198                   struct Row1011;ˇ"#},
16199        vec![
16200            DiffHunkStatusKind::Modified,
16201            DiffHunkStatusKind::Modified,
16202            DiffHunkStatusKind::Modified,
16203            DiffHunkStatusKind::Modified,
16204            DiffHunkStatusKind::Modified,
16205            DiffHunkStatusKind::Modified,
16206        ],
16207        indoc! {r#"struct Row;
16208                   ˇstruct Row1;
16209                   struct Row2;
16210                   ˇ
16211                   struct Row4;
16212                   ˇstruct Row5;
16213                   struct Row6;
16214                   ˇ
16215                   struct Row8;
16216                   ˇstruct Row9;
16217                   struct Row10;ˇ"#},
16218        base_text,
16219        &mut cx,
16220    );
16221}
16222
16223#[gpui::test]
16224async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16225    init_test(cx, |_| {});
16226    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16227    let base_text = indoc! {r#"
16228        one
16229
16230        two
16231        three
16232        "#};
16233
16234    cx.set_head_text(base_text);
16235    cx.set_state("\nˇ\n");
16236    cx.executor().run_until_parked();
16237    cx.update_editor(|editor, _window, cx| {
16238        editor.expand_selected_diff_hunks(cx);
16239    });
16240    cx.executor().run_until_parked();
16241    cx.update_editor(|editor, window, cx| {
16242        editor.backspace(&Default::default(), window, cx);
16243    });
16244    cx.run_until_parked();
16245    cx.assert_state_with_diff(
16246        indoc! {r#"
16247
16248        - two
16249        - threeˇ
16250        +
16251        "#}
16252        .to_string(),
16253    );
16254}
16255
16256#[gpui::test]
16257async fn test_deletion_reverts(cx: &mut TestAppContext) {
16258    init_test(cx, |_| {});
16259    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16260    let base_text = indoc! {r#"struct Row;
16261struct Row1;
16262struct Row2;
16263
16264struct Row4;
16265struct Row5;
16266struct Row6;
16267
16268struct Row8;
16269struct Row9;
16270struct Row10;"#};
16271
16272    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16273    assert_hunk_revert(
16274        indoc! {r#"struct Row;
16275                   struct Row2;
16276
16277                   ˇstruct Row4;
16278                   struct Row5;
16279                   struct Row6;
16280                   ˇ
16281                   struct Row8;
16282                   struct Row10;"#},
16283        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16284        indoc! {r#"struct Row;
16285                   struct Row2;
16286
16287                   ˇstruct Row4;
16288                   struct Row5;
16289                   struct Row6;
16290                   ˇ
16291                   struct Row8;
16292                   struct Row10;"#},
16293        base_text,
16294        &mut cx,
16295    );
16296    assert_hunk_revert(
16297        indoc! {r#"struct Row;
16298                   struct Row2;
16299
16300                   «ˇstruct Row4;
16301                   struct» Row5;
16302                   «struct Row6;
16303                   ˇ»
16304                   struct Row8;
16305                   struct Row10;"#},
16306        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16307        indoc! {r#"struct Row;
16308                   struct Row2;
16309
16310                   «ˇstruct Row4;
16311                   struct» Row5;
16312                   «struct Row6;
16313                   ˇ»
16314                   struct Row8;
16315                   struct Row10;"#},
16316        base_text,
16317        &mut cx,
16318    );
16319
16320    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16321    assert_hunk_revert(
16322        indoc! {r#"struct Row;
16323                   ˇstruct Row2;
16324
16325                   struct Row4;
16326                   struct Row5;
16327                   struct Row6;
16328
16329                   struct Row8;ˇ
16330                   struct Row10;"#},
16331        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16332        indoc! {r#"struct Row;
16333                   struct Row1;
16334                   ˇstruct Row2;
16335
16336                   struct Row4;
16337                   struct Row5;
16338                   struct Row6;
16339
16340                   struct Row8;ˇ
16341                   struct Row9;
16342                   struct Row10;"#},
16343        base_text,
16344        &mut cx,
16345    );
16346    assert_hunk_revert(
16347        indoc! {r#"struct Row;
16348                   struct Row2«ˇ;
16349                   struct Row4;
16350                   struct» Row5;
16351                   «struct Row6;
16352
16353                   struct Row8;ˇ»
16354                   struct Row10;"#},
16355        vec![
16356            DiffHunkStatusKind::Deleted,
16357            DiffHunkStatusKind::Deleted,
16358            DiffHunkStatusKind::Deleted,
16359        ],
16360        indoc! {r#"struct Row;
16361                   struct Row1;
16362                   struct Row2«ˇ;
16363
16364                   struct Row4;
16365                   struct» Row5;
16366                   «struct Row6;
16367
16368                   struct Row8;ˇ»
16369                   struct Row9;
16370                   struct Row10;"#},
16371        base_text,
16372        &mut cx,
16373    );
16374}
16375
16376#[gpui::test]
16377async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16378    init_test(cx, |_| {});
16379
16380    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16381    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16382    let base_text_3 =
16383        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16384
16385    let text_1 = edit_first_char_of_every_line(base_text_1);
16386    let text_2 = edit_first_char_of_every_line(base_text_2);
16387    let text_3 = edit_first_char_of_every_line(base_text_3);
16388
16389    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16390    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16391    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16392
16393    let multibuffer = cx.new(|cx| {
16394        let mut multibuffer = MultiBuffer::new(ReadWrite);
16395        multibuffer.push_excerpts(
16396            buffer_1.clone(),
16397            [
16398                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16399                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16400                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16401            ],
16402            cx,
16403        );
16404        multibuffer.push_excerpts(
16405            buffer_2.clone(),
16406            [
16407                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16408                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16409                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16410            ],
16411            cx,
16412        );
16413        multibuffer.push_excerpts(
16414            buffer_3.clone(),
16415            [
16416                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16417                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16418                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16419            ],
16420            cx,
16421        );
16422        multibuffer
16423    });
16424
16425    let fs = FakeFs::new(cx.executor());
16426    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16427    let (editor, cx) = cx
16428        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16429    editor.update_in(cx, |editor, _window, cx| {
16430        for (buffer, diff_base) in [
16431            (buffer_1.clone(), base_text_1),
16432            (buffer_2.clone(), base_text_2),
16433            (buffer_3.clone(), base_text_3),
16434        ] {
16435            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16436            editor
16437                .buffer
16438                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16439        }
16440    });
16441    cx.executor().run_until_parked();
16442
16443    editor.update_in(cx, |editor, window, cx| {
16444        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}");
16445        editor.select_all(&SelectAll, window, cx);
16446        editor.git_restore(&Default::default(), window, cx);
16447    });
16448    cx.executor().run_until_parked();
16449
16450    // When all ranges are selected, all buffer hunks are reverted.
16451    editor.update(cx, |editor, cx| {
16452        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");
16453    });
16454    buffer_1.update(cx, |buffer, _| {
16455        assert_eq!(buffer.text(), base_text_1);
16456    });
16457    buffer_2.update(cx, |buffer, _| {
16458        assert_eq!(buffer.text(), base_text_2);
16459    });
16460    buffer_3.update(cx, |buffer, _| {
16461        assert_eq!(buffer.text(), base_text_3);
16462    });
16463
16464    editor.update_in(cx, |editor, window, cx| {
16465        editor.undo(&Default::default(), window, cx);
16466    });
16467
16468    editor.update_in(cx, |editor, window, cx| {
16469        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16470            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16471        });
16472        editor.git_restore(&Default::default(), window, cx);
16473    });
16474
16475    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16476    // but not affect buffer_2 and its related excerpts.
16477    editor.update(cx, |editor, cx| {
16478        assert_eq!(
16479            editor.text(cx),
16480            "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}"
16481        );
16482    });
16483    buffer_1.update(cx, |buffer, _| {
16484        assert_eq!(buffer.text(), base_text_1);
16485    });
16486    buffer_2.update(cx, |buffer, _| {
16487        assert_eq!(
16488            buffer.text(),
16489            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16490        );
16491    });
16492    buffer_3.update(cx, |buffer, _| {
16493        assert_eq!(
16494            buffer.text(),
16495            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16496        );
16497    });
16498
16499    fn edit_first_char_of_every_line(text: &str) -> String {
16500        text.split('\n')
16501            .map(|line| format!("X{}", &line[1..]))
16502            .collect::<Vec<_>>()
16503            .join("\n")
16504    }
16505}
16506
16507#[gpui::test]
16508async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16509    init_test(cx, |_| {});
16510
16511    let cols = 4;
16512    let rows = 10;
16513    let sample_text_1 = sample_text(rows, cols, 'a');
16514    assert_eq!(
16515        sample_text_1,
16516        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16517    );
16518    let sample_text_2 = sample_text(rows, cols, 'l');
16519    assert_eq!(
16520        sample_text_2,
16521        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16522    );
16523    let sample_text_3 = sample_text(rows, cols, 'v');
16524    assert_eq!(
16525        sample_text_3,
16526        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16527    );
16528
16529    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16530    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16531    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16532
16533    let multi_buffer = cx.new(|cx| {
16534        let mut multibuffer = MultiBuffer::new(ReadWrite);
16535        multibuffer.push_excerpts(
16536            buffer_1.clone(),
16537            [
16538                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16539                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16540                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16541            ],
16542            cx,
16543        );
16544        multibuffer.push_excerpts(
16545            buffer_2.clone(),
16546            [
16547                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16548                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16549                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16550            ],
16551            cx,
16552        );
16553        multibuffer.push_excerpts(
16554            buffer_3.clone(),
16555            [
16556                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16557                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16558                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16559            ],
16560            cx,
16561        );
16562        multibuffer
16563    });
16564
16565    let fs = FakeFs::new(cx.executor());
16566    fs.insert_tree(
16567        "/a",
16568        json!({
16569            "main.rs": sample_text_1,
16570            "other.rs": sample_text_2,
16571            "lib.rs": sample_text_3,
16572        }),
16573    )
16574    .await;
16575    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16576    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16577    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16578    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16579        Editor::new(
16580            EditorMode::full(),
16581            multi_buffer,
16582            Some(project.clone()),
16583            window,
16584            cx,
16585        )
16586    });
16587    let multibuffer_item_id = workspace
16588        .update(cx, |workspace, window, cx| {
16589            assert!(
16590                workspace.active_item(cx).is_none(),
16591                "active item should be None before the first item is added"
16592            );
16593            workspace.add_item_to_active_pane(
16594                Box::new(multi_buffer_editor.clone()),
16595                None,
16596                true,
16597                window,
16598                cx,
16599            );
16600            let active_item = workspace
16601                .active_item(cx)
16602                .expect("should have an active item after adding the multi buffer");
16603            assert!(
16604                !active_item.is_singleton(cx),
16605                "A multi buffer was expected to active after adding"
16606            );
16607            active_item.item_id()
16608        })
16609        .unwrap();
16610    cx.executor().run_until_parked();
16611
16612    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16613        editor.change_selections(
16614            SelectionEffects::scroll(Autoscroll::Next),
16615            window,
16616            cx,
16617            |s| s.select_ranges(Some(1..2)),
16618        );
16619        editor.open_excerpts(&OpenExcerpts, window, cx);
16620    });
16621    cx.executor().run_until_parked();
16622    let first_item_id = workspace
16623        .update(cx, |workspace, window, cx| {
16624            let active_item = workspace
16625                .active_item(cx)
16626                .expect("should have an active item after navigating into the 1st buffer");
16627            let first_item_id = active_item.item_id();
16628            assert_ne!(
16629                first_item_id, multibuffer_item_id,
16630                "Should navigate into the 1st buffer and activate it"
16631            );
16632            assert!(
16633                active_item.is_singleton(cx),
16634                "New active item should be a singleton buffer"
16635            );
16636            assert_eq!(
16637                active_item
16638                    .act_as::<Editor>(cx)
16639                    .expect("should have navigated into an editor for the 1st buffer")
16640                    .read(cx)
16641                    .text(cx),
16642                sample_text_1
16643            );
16644
16645            workspace
16646                .go_back(workspace.active_pane().downgrade(), window, cx)
16647                .detach_and_log_err(cx);
16648
16649            first_item_id
16650        })
16651        .unwrap();
16652    cx.executor().run_until_parked();
16653    workspace
16654        .update(cx, |workspace, _, cx| {
16655            let active_item = workspace
16656                .active_item(cx)
16657                .expect("should have an active item after navigating back");
16658            assert_eq!(
16659                active_item.item_id(),
16660                multibuffer_item_id,
16661                "Should navigate back to the multi buffer"
16662            );
16663            assert!(!active_item.is_singleton(cx));
16664        })
16665        .unwrap();
16666
16667    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16668        editor.change_selections(
16669            SelectionEffects::scroll(Autoscroll::Next),
16670            window,
16671            cx,
16672            |s| s.select_ranges(Some(39..40)),
16673        );
16674        editor.open_excerpts(&OpenExcerpts, window, cx);
16675    });
16676    cx.executor().run_until_parked();
16677    let second_item_id = workspace
16678        .update(cx, |workspace, window, cx| {
16679            let active_item = workspace
16680                .active_item(cx)
16681                .expect("should have an active item after navigating into the 2nd buffer");
16682            let second_item_id = active_item.item_id();
16683            assert_ne!(
16684                second_item_id, multibuffer_item_id,
16685                "Should navigate away from the multibuffer"
16686            );
16687            assert_ne!(
16688                second_item_id, first_item_id,
16689                "Should navigate into the 2nd buffer and activate it"
16690            );
16691            assert!(
16692                active_item.is_singleton(cx),
16693                "New active item should be a singleton buffer"
16694            );
16695            assert_eq!(
16696                active_item
16697                    .act_as::<Editor>(cx)
16698                    .expect("should have navigated into an editor")
16699                    .read(cx)
16700                    .text(cx),
16701                sample_text_2
16702            );
16703
16704            workspace
16705                .go_back(workspace.active_pane().downgrade(), window, cx)
16706                .detach_and_log_err(cx);
16707
16708            second_item_id
16709        })
16710        .unwrap();
16711    cx.executor().run_until_parked();
16712    workspace
16713        .update(cx, |workspace, _, cx| {
16714            let active_item = workspace
16715                .active_item(cx)
16716                .expect("should have an active item after navigating back from the 2nd buffer");
16717            assert_eq!(
16718                active_item.item_id(),
16719                multibuffer_item_id,
16720                "Should navigate back from the 2nd buffer to the multi buffer"
16721            );
16722            assert!(!active_item.is_singleton(cx));
16723        })
16724        .unwrap();
16725
16726    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16727        editor.change_selections(
16728            SelectionEffects::scroll(Autoscroll::Next),
16729            window,
16730            cx,
16731            |s| s.select_ranges(Some(70..70)),
16732        );
16733        editor.open_excerpts(&OpenExcerpts, window, cx);
16734    });
16735    cx.executor().run_until_parked();
16736    workspace
16737        .update(cx, |workspace, window, cx| {
16738            let active_item = workspace
16739                .active_item(cx)
16740                .expect("should have an active item after navigating into the 3rd buffer");
16741            let third_item_id = active_item.item_id();
16742            assert_ne!(
16743                third_item_id, multibuffer_item_id,
16744                "Should navigate into the 3rd buffer and activate it"
16745            );
16746            assert_ne!(third_item_id, first_item_id);
16747            assert_ne!(third_item_id, second_item_id);
16748            assert!(
16749                active_item.is_singleton(cx),
16750                "New active item should be a singleton buffer"
16751            );
16752            assert_eq!(
16753                active_item
16754                    .act_as::<Editor>(cx)
16755                    .expect("should have navigated into an editor")
16756                    .read(cx)
16757                    .text(cx),
16758                sample_text_3
16759            );
16760
16761            workspace
16762                .go_back(workspace.active_pane().downgrade(), window, cx)
16763                .detach_and_log_err(cx);
16764        })
16765        .unwrap();
16766    cx.executor().run_until_parked();
16767    workspace
16768        .update(cx, |workspace, _, cx| {
16769            let active_item = workspace
16770                .active_item(cx)
16771                .expect("should have an active item after navigating back from the 3rd buffer");
16772            assert_eq!(
16773                active_item.item_id(),
16774                multibuffer_item_id,
16775                "Should navigate back from the 3rd buffer to the multi buffer"
16776            );
16777            assert!(!active_item.is_singleton(cx));
16778        })
16779        .unwrap();
16780}
16781
16782#[gpui::test]
16783async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16784    init_test(cx, |_| {});
16785
16786    let mut cx = EditorTestContext::new(cx).await;
16787
16788    let diff_base = r#"
16789        use some::mod;
16790
16791        const A: u32 = 42;
16792
16793        fn main() {
16794            println!("hello");
16795
16796            println!("world");
16797        }
16798        "#
16799    .unindent();
16800
16801    cx.set_state(
16802        &r#"
16803        use some::modified;
16804
16805        ˇ
16806        fn main() {
16807            println!("hello there");
16808
16809            println!("around the");
16810            println!("world");
16811        }
16812        "#
16813        .unindent(),
16814    );
16815
16816    cx.set_head_text(&diff_base);
16817    executor.run_until_parked();
16818
16819    cx.update_editor(|editor, window, cx| {
16820        editor.go_to_next_hunk(&GoToHunk, window, cx);
16821        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16822    });
16823    executor.run_until_parked();
16824    cx.assert_state_with_diff(
16825        r#"
16826          use some::modified;
16827
16828
16829          fn main() {
16830        -     println!("hello");
16831        + ˇ    println!("hello there");
16832
16833              println!("around the");
16834              println!("world");
16835          }
16836        "#
16837        .unindent(),
16838    );
16839
16840    cx.update_editor(|editor, window, cx| {
16841        for _ in 0..2 {
16842            editor.go_to_next_hunk(&GoToHunk, window, cx);
16843            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16844        }
16845    });
16846    executor.run_until_parked();
16847    cx.assert_state_with_diff(
16848        r#"
16849        - use some::mod;
16850        + ˇuse some::modified;
16851
16852
16853          fn main() {
16854        -     println!("hello");
16855        +     println!("hello there");
16856
16857        +     println!("around the");
16858              println!("world");
16859          }
16860        "#
16861        .unindent(),
16862    );
16863
16864    cx.update_editor(|editor, window, cx| {
16865        editor.go_to_next_hunk(&GoToHunk, window, cx);
16866        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16867    });
16868    executor.run_until_parked();
16869    cx.assert_state_with_diff(
16870        r#"
16871        - use some::mod;
16872        + use some::modified;
16873
16874        - const A: u32 = 42;
16875          ˇ
16876          fn main() {
16877        -     println!("hello");
16878        +     println!("hello there");
16879
16880        +     println!("around the");
16881              println!("world");
16882          }
16883        "#
16884        .unindent(),
16885    );
16886
16887    cx.update_editor(|editor, window, cx| {
16888        editor.cancel(&Cancel, window, cx);
16889    });
16890
16891    cx.assert_state_with_diff(
16892        r#"
16893          use some::modified;
16894
16895          ˇ
16896          fn main() {
16897              println!("hello there");
16898
16899              println!("around the");
16900              println!("world");
16901          }
16902        "#
16903        .unindent(),
16904    );
16905}
16906
16907#[gpui::test]
16908async fn test_diff_base_change_with_expanded_diff_hunks(
16909    executor: BackgroundExecutor,
16910    cx: &mut TestAppContext,
16911) {
16912    init_test(cx, |_| {});
16913
16914    let mut cx = EditorTestContext::new(cx).await;
16915
16916    let diff_base = r#"
16917        use some::mod1;
16918        use some::mod2;
16919
16920        const A: u32 = 42;
16921        const B: u32 = 42;
16922        const C: u32 = 42;
16923
16924        fn main() {
16925            println!("hello");
16926
16927            println!("world");
16928        }
16929        "#
16930    .unindent();
16931
16932    cx.set_state(
16933        &r#"
16934        use some::mod2;
16935
16936        const A: u32 = 42;
16937        const C: u32 = 42;
16938
16939        fn main(ˇ) {
16940            //println!("hello");
16941
16942            println!("world");
16943            //
16944            //
16945        }
16946        "#
16947        .unindent(),
16948    );
16949
16950    cx.set_head_text(&diff_base);
16951    executor.run_until_parked();
16952
16953    cx.update_editor(|editor, window, cx| {
16954        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16955    });
16956    executor.run_until_parked();
16957    cx.assert_state_with_diff(
16958        r#"
16959        - use some::mod1;
16960          use some::mod2;
16961
16962          const A: u32 = 42;
16963        - const B: u32 = 42;
16964          const C: u32 = 42;
16965
16966          fn main(ˇ) {
16967        -     println!("hello");
16968        +     //println!("hello");
16969
16970              println!("world");
16971        +     //
16972        +     //
16973          }
16974        "#
16975        .unindent(),
16976    );
16977
16978    cx.set_head_text("new diff base!");
16979    executor.run_until_parked();
16980    cx.assert_state_with_diff(
16981        r#"
16982        - new diff base!
16983        + use some::mod2;
16984        +
16985        + const A: u32 = 42;
16986        + const C: u32 = 42;
16987        +
16988        + fn main(ˇ) {
16989        +     //println!("hello");
16990        +
16991        +     println!("world");
16992        +     //
16993        +     //
16994        + }
16995        "#
16996        .unindent(),
16997    );
16998}
16999
17000#[gpui::test]
17001async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17002    init_test(cx, |_| {});
17003
17004    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17005    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17006    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17007    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17008    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17009    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17010
17011    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17012    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17013    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17014
17015    let multi_buffer = cx.new(|cx| {
17016        let mut multibuffer = MultiBuffer::new(ReadWrite);
17017        multibuffer.push_excerpts(
17018            buffer_1.clone(),
17019            [
17020                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17021                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17022                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17023            ],
17024            cx,
17025        );
17026        multibuffer.push_excerpts(
17027            buffer_2.clone(),
17028            [
17029                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17030                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17031                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17032            ],
17033            cx,
17034        );
17035        multibuffer.push_excerpts(
17036            buffer_3.clone(),
17037            [
17038                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17039                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17040                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17041            ],
17042            cx,
17043        );
17044        multibuffer
17045    });
17046
17047    let editor =
17048        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17049    editor
17050        .update(cx, |editor, _window, cx| {
17051            for (buffer, diff_base) in [
17052                (buffer_1.clone(), file_1_old),
17053                (buffer_2.clone(), file_2_old),
17054                (buffer_3.clone(), file_3_old),
17055            ] {
17056                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17057                editor
17058                    .buffer
17059                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17060            }
17061        })
17062        .unwrap();
17063
17064    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17065    cx.run_until_parked();
17066
17067    cx.assert_editor_state(
17068        &"
17069            ˇaaa
17070            ccc
17071            ddd
17072
17073            ggg
17074            hhh
17075
17076
17077            lll
17078            mmm
17079            NNN
17080
17081            qqq
17082            rrr
17083
17084            uuu
17085            111
17086            222
17087            333
17088
17089            666
17090            777
17091
17092            000
17093            !!!"
17094        .unindent(),
17095    );
17096
17097    cx.update_editor(|editor, window, cx| {
17098        editor.select_all(&SelectAll, window, cx);
17099        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17100    });
17101    cx.executor().run_until_parked();
17102
17103    cx.assert_state_with_diff(
17104        "
17105            «aaa
17106          - bbb
17107            ccc
17108            ddd
17109
17110            ggg
17111            hhh
17112
17113
17114            lll
17115            mmm
17116          - nnn
17117          + NNN
17118
17119            qqq
17120            rrr
17121
17122            uuu
17123            111
17124            222
17125            333
17126
17127          + 666
17128            777
17129
17130            000
17131            !!!ˇ»"
17132            .unindent(),
17133    );
17134}
17135
17136#[gpui::test]
17137async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17138    init_test(cx, |_| {});
17139
17140    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17141    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17142
17143    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17144    let multi_buffer = cx.new(|cx| {
17145        let mut multibuffer = MultiBuffer::new(ReadWrite);
17146        multibuffer.push_excerpts(
17147            buffer.clone(),
17148            [
17149                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17150                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17151                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17152            ],
17153            cx,
17154        );
17155        multibuffer
17156    });
17157
17158    let editor =
17159        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17160    editor
17161        .update(cx, |editor, _window, cx| {
17162            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17163            editor
17164                .buffer
17165                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17166        })
17167        .unwrap();
17168
17169    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17170    cx.run_until_parked();
17171
17172    cx.update_editor(|editor, window, cx| {
17173        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17174    });
17175    cx.executor().run_until_parked();
17176
17177    // When the start of a hunk coincides with the start of its excerpt,
17178    // the hunk is expanded. When the start of a a hunk is earlier than
17179    // the start of its excerpt, the hunk is not expanded.
17180    cx.assert_state_with_diff(
17181        "
17182            ˇaaa
17183          - bbb
17184          + BBB
17185
17186          - ddd
17187          - eee
17188          + DDD
17189          + EEE
17190            fff
17191
17192            iii
17193        "
17194        .unindent(),
17195    );
17196}
17197
17198#[gpui::test]
17199async fn test_edits_around_expanded_insertion_hunks(
17200    executor: BackgroundExecutor,
17201    cx: &mut TestAppContext,
17202) {
17203    init_test(cx, |_| {});
17204
17205    let mut cx = EditorTestContext::new(cx).await;
17206
17207    let diff_base = r#"
17208        use some::mod1;
17209        use some::mod2;
17210
17211        const A: u32 = 42;
17212
17213        fn main() {
17214            println!("hello");
17215
17216            println!("world");
17217        }
17218        "#
17219    .unindent();
17220    executor.run_until_parked();
17221    cx.set_state(
17222        &r#"
17223        use some::mod1;
17224        use some::mod2;
17225
17226        const A: u32 = 42;
17227        const B: u32 = 42;
17228        const C: u32 = 42;
17229        ˇ
17230
17231        fn main() {
17232            println!("hello");
17233
17234            println!("world");
17235        }
17236        "#
17237        .unindent(),
17238    );
17239
17240    cx.set_head_text(&diff_base);
17241    executor.run_until_parked();
17242
17243    cx.update_editor(|editor, window, cx| {
17244        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17245    });
17246    executor.run_until_parked();
17247
17248    cx.assert_state_with_diff(
17249        r#"
17250        use some::mod1;
17251        use some::mod2;
17252
17253        const A: u32 = 42;
17254      + const B: u32 = 42;
17255      + const C: u32 = 42;
17256      + ˇ
17257
17258        fn main() {
17259            println!("hello");
17260
17261            println!("world");
17262        }
17263      "#
17264        .unindent(),
17265    );
17266
17267    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17268    executor.run_until_parked();
17269
17270    cx.assert_state_with_diff(
17271        r#"
17272        use some::mod1;
17273        use some::mod2;
17274
17275        const A: u32 = 42;
17276      + const B: u32 = 42;
17277      + const C: u32 = 42;
17278      + const D: u32 = 42;
17279      + ˇ
17280
17281        fn main() {
17282            println!("hello");
17283
17284            println!("world");
17285        }
17286      "#
17287        .unindent(),
17288    );
17289
17290    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17291    executor.run_until_parked();
17292
17293    cx.assert_state_with_diff(
17294        r#"
17295        use some::mod1;
17296        use some::mod2;
17297
17298        const A: u32 = 42;
17299      + const B: u32 = 42;
17300      + const C: u32 = 42;
17301      + const D: u32 = 42;
17302      + const E: u32 = 42;
17303      + ˇ
17304
17305        fn main() {
17306            println!("hello");
17307
17308            println!("world");
17309        }
17310      "#
17311        .unindent(),
17312    );
17313
17314    cx.update_editor(|editor, window, cx| {
17315        editor.delete_line(&DeleteLine, window, cx);
17316    });
17317    executor.run_until_parked();
17318
17319    cx.assert_state_with_diff(
17320        r#"
17321        use some::mod1;
17322        use some::mod2;
17323
17324        const A: u32 = 42;
17325      + const B: u32 = 42;
17326      + const C: u32 = 42;
17327      + const D: u32 = 42;
17328      + const E: u32 = 42;
17329        ˇ
17330        fn main() {
17331            println!("hello");
17332
17333            println!("world");
17334        }
17335      "#
17336        .unindent(),
17337    );
17338
17339    cx.update_editor(|editor, window, cx| {
17340        editor.move_up(&MoveUp, window, cx);
17341        editor.delete_line(&DeleteLine, window, cx);
17342        editor.move_up(&MoveUp, window, cx);
17343        editor.delete_line(&DeleteLine, window, cx);
17344        editor.move_up(&MoveUp, window, cx);
17345        editor.delete_line(&DeleteLine, window, cx);
17346    });
17347    executor.run_until_parked();
17348    cx.assert_state_with_diff(
17349        r#"
17350        use some::mod1;
17351        use some::mod2;
17352
17353        const A: u32 = 42;
17354      + const B: u32 = 42;
17355        ˇ
17356        fn main() {
17357            println!("hello");
17358
17359            println!("world");
17360        }
17361      "#
17362        .unindent(),
17363    );
17364
17365    cx.update_editor(|editor, window, cx| {
17366        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17367        editor.delete_line(&DeleteLine, window, cx);
17368    });
17369    executor.run_until_parked();
17370    cx.assert_state_with_diff(
17371        r#"
17372        ˇ
17373        fn main() {
17374            println!("hello");
17375
17376            println!("world");
17377        }
17378      "#
17379        .unindent(),
17380    );
17381}
17382
17383#[gpui::test]
17384async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17385    init_test(cx, |_| {});
17386
17387    let mut cx = EditorTestContext::new(cx).await;
17388    cx.set_head_text(indoc! { "
17389        one
17390        two
17391        three
17392        four
17393        five
17394        "
17395    });
17396    cx.set_state(indoc! { "
17397        one
17398        ˇthree
17399        five
17400    "});
17401    cx.run_until_parked();
17402    cx.update_editor(|editor, window, cx| {
17403        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17404    });
17405    cx.assert_state_with_diff(
17406        indoc! { "
17407        one
17408      - two
17409        ˇthree
17410      - four
17411        five
17412    "}
17413        .to_string(),
17414    );
17415    cx.update_editor(|editor, window, cx| {
17416        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17417    });
17418
17419    cx.assert_state_with_diff(
17420        indoc! { "
17421        one
17422        ˇthree
17423        five
17424    "}
17425        .to_string(),
17426    );
17427
17428    cx.set_state(indoc! { "
17429        one
17430        ˇTWO
17431        three
17432        four
17433        five
17434    "});
17435    cx.run_until_parked();
17436    cx.update_editor(|editor, window, cx| {
17437        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17438    });
17439
17440    cx.assert_state_with_diff(
17441        indoc! { "
17442            one
17443          - two
17444          + ˇTWO
17445            three
17446            four
17447            five
17448        "}
17449        .to_string(),
17450    );
17451    cx.update_editor(|editor, window, cx| {
17452        editor.move_up(&Default::default(), window, cx);
17453        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17454    });
17455    cx.assert_state_with_diff(
17456        indoc! { "
17457            one
17458            ˇTWO
17459            three
17460            four
17461            five
17462        "}
17463        .to_string(),
17464    );
17465}
17466
17467#[gpui::test]
17468async fn test_edits_around_expanded_deletion_hunks(
17469    executor: BackgroundExecutor,
17470    cx: &mut TestAppContext,
17471) {
17472    init_test(cx, |_| {});
17473
17474    let mut cx = EditorTestContext::new(cx).await;
17475
17476    let diff_base = r#"
17477        use some::mod1;
17478        use some::mod2;
17479
17480        const A: u32 = 42;
17481        const B: u32 = 42;
17482        const C: u32 = 42;
17483
17484
17485        fn main() {
17486            println!("hello");
17487
17488            println!("world");
17489        }
17490    "#
17491    .unindent();
17492    executor.run_until_parked();
17493    cx.set_state(
17494        &r#"
17495        use some::mod1;
17496        use some::mod2;
17497
17498        ˇconst B: u32 = 42;
17499        const C: u32 = 42;
17500
17501
17502        fn main() {
17503            println!("hello");
17504
17505            println!("world");
17506        }
17507        "#
17508        .unindent(),
17509    );
17510
17511    cx.set_head_text(&diff_base);
17512    executor.run_until_parked();
17513
17514    cx.update_editor(|editor, window, cx| {
17515        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17516    });
17517    executor.run_until_parked();
17518
17519    cx.assert_state_with_diff(
17520        r#"
17521        use some::mod1;
17522        use some::mod2;
17523
17524      - const A: u32 = 42;
17525        ˇconst B: u32 = 42;
17526        const C: u32 = 42;
17527
17528
17529        fn main() {
17530            println!("hello");
17531
17532            println!("world");
17533        }
17534      "#
17535        .unindent(),
17536    );
17537
17538    cx.update_editor(|editor, window, cx| {
17539        editor.delete_line(&DeleteLine, window, cx);
17540    });
17541    executor.run_until_parked();
17542    cx.assert_state_with_diff(
17543        r#"
17544        use some::mod1;
17545        use some::mod2;
17546
17547      - const A: u32 = 42;
17548      - const B: u32 = 42;
17549        ˇconst C: u32 = 42;
17550
17551
17552        fn main() {
17553            println!("hello");
17554
17555            println!("world");
17556        }
17557      "#
17558        .unindent(),
17559    );
17560
17561    cx.update_editor(|editor, window, cx| {
17562        editor.delete_line(&DeleteLine, window, cx);
17563    });
17564    executor.run_until_parked();
17565    cx.assert_state_with_diff(
17566        r#"
17567        use some::mod1;
17568        use some::mod2;
17569
17570      - const A: u32 = 42;
17571      - const B: u32 = 42;
17572      - const C: u32 = 42;
17573        ˇ
17574
17575        fn main() {
17576            println!("hello");
17577
17578            println!("world");
17579        }
17580      "#
17581        .unindent(),
17582    );
17583
17584    cx.update_editor(|editor, window, cx| {
17585        editor.handle_input("replacement", window, cx);
17586    });
17587    executor.run_until_parked();
17588    cx.assert_state_with_diff(
17589        r#"
17590        use some::mod1;
17591        use some::mod2;
17592
17593      - const A: u32 = 42;
17594      - const B: u32 = 42;
17595      - const C: u32 = 42;
17596      -
17597      + replacementˇ
17598
17599        fn main() {
17600            println!("hello");
17601
17602            println!("world");
17603        }
17604      "#
17605        .unindent(),
17606    );
17607}
17608
17609#[gpui::test]
17610async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17611    init_test(cx, |_| {});
17612
17613    let mut cx = EditorTestContext::new(cx).await;
17614
17615    let base_text = r#"
17616        one
17617        two
17618        three
17619        four
17620        five
17621    "#
17622    .unindent();
17623    executor.run_until_parked();
17624    cx.set_state(
17625        &r#"
17626        one
17627        two
17628        fˇour
17629        five
17630        "#
17631        .unindent(),
17632    );
17633
17634    cx.set_head_text(&base_text);
17635    executor.run_until_parked();
17636
17637    cx.update_editor(|editor, window, cx| {
17638        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17639    });
17640    executor.run_until_parked();
17641
17642    cx.assert_state_with_diff(
17643        r#"
17644          one
17645          two
17646        - three
17647          fˇour
17648          five
17649        "#
17650        .unindent(),
17651    );
17652
17653    cx.update_editor(|editor, window, cx| {
17654        editor.backspace(&Backspace, window, cx);
17655        editor.backspace(&Backspace, window, cx);
17656    });
17657    executor.run_until_parked();
17658    cx.assert_state_with_diff(
17659        r#"
17660          one
17661          two
17662        - threeˇ
17663        - four
17664        + our
17665          five
17666        "#
17667        .unindent(),
17668    );
17669}
17670
17671#[gpui::test]
17672async fn test_edit_after_expanded_modification_hunk(
17673    executor: BackgroundExecutor,
17674    cx: &mut TestAppContext,
17675) {
17676    init_test(cx, |_| {});
17677
17678    let mut cx = EditorTestContext::new(cx).await;
17679
17680    let diff_base = r#"
17681        use some::mod1;
17682        use some::mod2;
17683
17684        const A: u32 = 42;
17685        const B: u32 = 42;
17686        const C: u32 = 42;
17687        const D: u32 = 42;
17688
17689
17690        fn main() {
17691            println!("hello");
17692
17693            println!("world");
17694        }"#
17695    .unindent();
17696
17697    cx.set_state(
17698        &r#"
17699        use some::mod1;
17700        use some::mod2;
17701
17702        const A: u32 = 42;
17703        const B: u32 = 42;
17704        const C: u32 = 43ˇ
17705        const D: u32 = 42;
17706
17707
17708        fn main() {
17709            println!("hello");
17710
17711            println!("world");
17712        }"#
17713        .unindent(),
17714    );
17715
17716    cx.set_head_text(&diff_base);
17717    executor.run_until_parked();
17718    cx.update_editor(|editor, window, cx| {
17719        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17720    });
17721    executor.run_until_parked();
17722
17723    cx.assert_state_with_diff(
17724        r#"
17725        use some::mod1;
17726        use some::mod2;
17727
17728        const A: u32 = 42;
17729        const B: u32 = 42;
17730      - const C: u32 = 42;
17731      + const C: u32 = 43ˇ
17732        const D: u32 = 42;
17733
17734
17735        fn main() {
17736            println!("hello");
17737
17738            println!("world");
17739        }"#
17740        .unindent(),
17741    );
17742
17743    cx.update_editor(|editor, window, cx| {
17744        editor.handle_input("\nnew_line\n", window, cx);
17745    });
17746    executor.run_until_parked();
17747
17748    cx.assert_state_with_diff(
17749        r#"
17750        use some::mod1;
17751        use some::mod2;
17752
17753        const A: u32 = 42;
17754        const B: u32 = 42;
17755      - const C: u32 = 42;
17756      + const C: u32 = 43
17757      + new_line
17758      + ˇ
17759        const D: u32 = 42;
17760
17761
17762        fn main() {
17763            println!("hello");
17764
17765            println!("world");
17766        }"#
17767        .unindent(),
17768    );
17769}
17770
17771#[gpui::test]
17772async fn test_stage_and_unstage_added_file_hunk(
17773    executor: BackgroundExecutor,
17774    cx: &mut TestAppContext,
17775) {
17776    init_test(cx, |_| {});
17777
17778    let mut cx = EditorTestContext::new(cx).await;
17779    cx.update_editor(|editor, _, cx| {
17780        editor.set_expand_all_diff_hunks(cx);
17781    });
17782
17783    let working_copy = r#"
17784            ˇfn main() {
17785                println!("hello, world!");
17786            }
17787        "#
17788    .unindent();
17789
17790    cx.set_state(&working_copy);
17791    executor.run_until_parked();
17792
17793    cx.assert_state_with_diff(
17794        r#"
17795            + ˇfn main() {
17796            +     println!("hello, world!");
17797            + }
17798        "#
17799        .unindent(),
17800    );
17801    cx.assert_index_text(None);
17802
17803    cx.update_editor(|editor, window, cx| {
17804        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17805    });
17806    executor.run_until_parked();
17807    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17808    cx.assert_state_with_diff(
17809        r#"
17810            + ˇfn main() {
17811            +     println!("hello, world!");
17812            + }
17813        "#
17814        .unindent(),
17815    );
17816
17817    cx.update_editor(|editor, window, cx| {
17818        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17819    });
17820    executor.run_until_parked();
17821    cx.assert_index_text(None);
17822}
17823
17824async fn setup_indent_guides_editor(
17825    text: &str,
17826    cx: &mut TestAppContext,
17827) -> (BufferId, EditorTestContext) {
17828    init_test(cx, |_| {});
17829
17830    let mut cx = EditorTestContext::new(cx).await;
17831
17832    let buffer_id = cx.update_editor(|editor, window, cx| {
17833        editor.set_text(text, window, cx);
17834        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17835
17836        buffer_ids[0]
17837    });
17838
17839    (buffer_id, cx)
17840}
17841
17842fn assert_indent_guides(
17843    range: Range<u32>,
17844    expected: Vec<IndentGuide>,
17845    active_indices: Option<Vec<usize>>,
17846    cx: &mut EditorTestContext,
17847) {
17848    let indent_guides = cx.update_editor(|editor, window, cx| {
17849        let snapshot = editor.snapshot(window, cx).display_snapshot;
17850        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17851            editor,
17852            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17853            true,
17854            &snapshot,
17855            cx,
17856        );
17857
17858        indent_guides.sort_by(|a, b| {
17859            a.depth.cmp(&b.depth).then(
17860                a.start_row
17861                    .cmp(&b.start_row)
17862                    .then(a.end_row.cmp(&b.end_row)),
17863            )
17864        });
17865        indent_guides
17866    });
17867
17868    if let Some(expected) = active_indices {
17869        let active_indices = cx.update_editor(|editor, window, cx| {
17870            let snapshot = editor.snapshot(window, cx).display_snapshot;
17871            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17872        });
17873
17874        assert_eq!(
17875            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17876            expected,
17877            "Active indent guide indices do not match"
17878        );
17879    }
17880
17881    assert_eq!(indent_guides, expected, "Indent guides do not match");
17882}
17883
17884fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17885    IndentGuide {
17886        buffer_id,
17887        start_row: MultiBufferRow(start_row),
17888        end_row: MultiBufferRow(end_row),
17889        depth,
17890        tab_size: 4,
17891        settings: IndentGuideSettings {
17892            enabled: true,
17893            line_width: 1,
17894            active_line_width: 1,
17895            ..Default::default()
17896        },
17897    }
17898}
17899
17900#[gpui::test]
17901async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17902    let (buffer_id, mut cx) = setup_indent_guides_editor(
17903        &"
17904        fn main() {
17905            let a = 1;
17906        }"
17907        .unindent(),
17908        cx,
17909    )
17910    .await;
17911
17912    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17913}
17914
17915#[gpui::test]
17916async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17917    let (buffer_id, mut cx) = setup_indent_guides_editor(
17918        &"
17919        fn main() {
17920            let a = 1;
17921            let b = 2;
17922        }"
17923        .unindent(),
17924        cx,
17925    )
17926    .await;
17927
17928    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17929}
17930
17931#[gpui::test]
17932async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17933    let (buffer_id, mut cx) = setup_indent_guides_editor(
17934        &"
17935        fn main() {
17936            let a = 1;
17937            if a == 3 {
17938                let b = 2;
17939            } else {
17940                let c = 3;
17941            }
17942        }"
17943        .unindent(),
17944        cx,
17945    )
17946    .await;
17947
17948    assert_indent_guides(
17949        0..8,
17950        vec![
17951            indent_guide(buffer_id, 1, 6, 0),
17952            indent_guide(buffer_id, 3, 3, 1),
17953            indent_guide(buffer_id, 5, 5, 1),
17954        ],
17955        None,
17956        &mut cx,
17957    );
17958}
17959
17960#[gpui::test]
17961async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17962    let (buffer_id, mut cx) = setup_indent_guides_editor(
17963        &"
17964        fn main() {
17965            let a = 1;
17966                let b = 2;
17967            let c = 3;
17968        }"
17969        .unindent(),
17970        cx,
17971    )
17972    .await;
17973
17974    assert_indent_guides(
17975        0..5,
17976        vec![
17977            indent_guide(buffer_id, 1, 3, 0),
17978            indent_guide(buffer_id, 2, 2, 1),
17979        ],
17980        None,
17981        &mut cx,
17982    );
17983}
17984
17985#[gpui::test]
17986async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17987    let (buffer_id, mut cx) = setup_indent_guides_editor(
17988        &"
17989        fn main() {
17990            let a = 1;
17991
17992            let c = 3;
17993        }"
17994        .unindent(),
17995        cx,
17996    )
17997    .await;
17998
17999    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18000}
18001
18002#[gpui::test]
18003async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18004    let (buffer_id, mut cx) = setup_indent_guides_editor(
18005        &"
18006        fn main() {
18007            let a = 1;
18008
18009            let c = 3;
18010
18011            if a == 3 {
18012                let b = 2;
18013            } else {
18014                let c = 3;
18015            }
18016        }"
18017        .unindent(),
18018        cx,
18019    )
18020    .await;
18021
18022    assert_indent_guides(
18023        0..11,
18024        vec![
18025            indent_guide(buffer_id, 1, 9, 0),
18026            indent_guide(buffer_id, 6, 6, 1),
18027            indent_guide(buffer_id, 8, 8, 1),
18028        ],
18029        None,
18030        &mut cx,
18031    );
18032}
18033
18034#[gpui::test]
18035async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18036    let (buffer_id, mut cx) = setup_indent_guides_editor(
18037        &"
18038        fn main() {
18039            let a = 1;
18040
18041            let c = 3;
18042
18043            if a == 3 {
18044                let b = 2;
18045            } else {
18046                let c = 3;
18047            }
18048        }"
18049        .unindent(),
18050        cx,
18051    )
18052    .await;
18053
18054    assert_indent_guides(
18055        1..11,
18056        vec![
18057            indent_guide(buffer_id, 1, 9, 0),
18058            indent_guide(buffer_id, 6, 6, 1),
18059            indent_guide(buffer_id, 8, 8, 1),
18060        ],
18061        None,
18062        &mut cx,
18063    );
18064}
18065
18066#[gpui::test]
18067async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18068    let (buffer_id, mut cx) = setup_indent_guides_editor(
18069        &"
18070        fn main() {
18071            let a = 1;
18072
18073            let c = 3;
18074
18075            if a == 3 {
18076                let b = 2;
18077            } else {
18078                let c = 3;
18079            }
18080        }"
18081        .unindent(),
18082        cx,
18083    )
18084    .await;
18085
18086    assert_indent_guides(
18087        1..10,
18088        vec![
18089            indent_guide(buffer_id, 1, 9, 0),
18090            indent_guide(buffer_id, 6, 6, 1),
18091            indent_guide(buffer_id, 8, 8, 1),
18092        ],
18093        None,
18094        &mut cx,
18095    );
18096}
18097
18098#[gpui::test]
18099async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18100    let (buffer_id, mut cx) = setup_indent_guides_editor(
18101        &"
18102        fn main() {
18103            if a {
18104                b(
18105                    c,
18106                    d,
18107                )
18108            } else {
18109                e(
18110                    f
18111                )
18112            }
18113        }"
18114        .unindent(),
18115        cx,
18116    )
18117    .await;
18118
18119    assert_indent_guides(
18120        0..11,
18121        vec![
18122            indent_guide(buffer_id, 1, 10, 0),
18123            indent_guide(buffer_id, 2, 5, 1),
18124            indent_guide(buffer_id, 7, 9, 1),
18125            indent_guide(buffer_id, 3, 4, 2),
18126            indent_guide(buffer_id, 8, 8, 2),
18127        ],
18128        None,
18129        &mut cx,
18130    );
18131
18132    cx.update_editor(|editor, window, cx| {
18133        editor.fold_at(MultiBufferRow(2), window, cx);
18134        assert_eq!(
18135            editor.display_text(cx),
18136            "
18137            fn main() {
18138                if a {
18139                    b(⋯
18140                    )
18141                } else {
18142                    e(
18143                        f
18144                    )
18145                }
18146            }"
18147            .unindent()
18148        );
18149    });
18150
18151    assert_indent_guides(
18152        0..11,
18153        vec![
18154            indent_guide(buffer_id, 1, 10, 0),
18155            indent_guide(buffer_id, 2, 5, 1),
18156            indent_guide(buffer_id, 7, 9, 1),
18157            indent_guide(buffer_id, 8, 8, 2),
18158        ],
18159        None,
18160        &mut cx,
18161    );
18162}
18163
18164#[gpui::test]
18165async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18166    let (buffer_id, mut cx) = setup_indent_guides_editor(
18167        &"
18168        block1
18169            block2
18170                block3
18171                    block4
18172            block2
18173        block1
18174        block1"
18175            .unindent(),
18176        cx,
18177    )
18178    .await;
18179
18180    assert_indent_guides(
18181        1..10,
18182        vec![
18183            indent_guide(buffer_id, 1, 4, 0),
18184            indent_guide(buffer_id, 2, 3, 1),
18185            indent_guide(buffer_id, 3, 3, 2),
18186        ],
18187        None,
18188        &mut cx,
18189    );
18190}
18191
18192#[gpui::test]
18193async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18194    let (buffer_id, mut cx) = setup_indent_guides_editor(
18195        &"
18196        block1
18197            block2
18198                block3
18199
18200        block1
18201        block1"
18202            .unindent(),
18203        cx,
18204    )
18205    .await;
18206
18207    assert_indent_guides(
18208        0..6,
18209        vec![
18210            indent_guide(buffer_id, 1, 2, 0),
18211            indent_guide(buffer_id, 2, 2, 1),
18212        ],
18213        None,
18214        &mut cx,
18215    );
18216}
18217
18218#[gpui::test]
18219async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18220    let (buffer_id, mut cx) = setup_indent_guides_editor(
18221        &"
18222        function component() {
18223        \treturn (
18224        \t\t\t
18225        \t\t<div>
18226        \t\t\t<abc></abc>
18227        \t\t</div>
18228        \t)
18229        }"
18230        .unindent(),
18231        cx,
18232    )
18233    .await;
18234
18235    assert_indent_guides(
18236        0..8,
18237        vec![
18238            indent_guide(buffer_id, 1, 6, 0),
18239            indent_guide(buffer_id, 2, 5, 1),
18240            indent_guide(buffer_id, 4, 4, 2),
18241        ],
18242        None,
18243        &mut cx,
18244    );
18245}
18246
18247#[gpui::test]
18248async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18249    let (buffer_id, mut cx) = setup_indent_guides_editor(
18250        &"
18251        function component() {
18252        \treturn (
18253        \t
18254        \t\t<div>
18255        \t\t\t<abc></abc>
18256        \t\t</div>
18257        \t)
18258        }"
18259        .unindent(),
18260        cx,
18261    )
18262    .await;
18263
18264    assert_indent_guides(
18265        0..8,
18266        vec![
18267            indent_guide(buffer_id, 1, 6, 0),
18268            indent_guide(buffer_id, 2, 5, 1),
18269            indent_guide(buffer_id, 4, 4, 2),
18270        ],
18271        None,
18272        &mut cx,
18273    );
18274}
18275
18276#[gpui::test]
18277async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18278    let (buffer_id, mut cx) = setup_indent_guides_editor(
18279        &"
18280        block1
18281
18282
18283
18284            block2
18285        "
18286        .unindent(),
18287        cx,
18288    )
18289    .await;
18290
18291    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18292}
18293
18294#[gpui::test]
18295async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18296    let (buffer_id, mut cx) = setup_indent_guides_editor(
18297        &"
18298        def a:
18299        \tb = 3
18300        \tif True:
18301        \t\tc = 4
18302        \t\td = 5
18303        \tprint(b)
18304        "
18305        .unindent(),
18306        cx,
18307    )
18308    .await;
18309
18310    assert_indent_guides(
18311        0..6,
18312        vec![
18313            indent_guide(buffer_id, 1, 5, 0),
18314            indent_guide(buffer_id, 3, 4, 1),
18315        ],
18316        None,
18317        &mut cx,
18318    );
18319}
18320
18321#[gpui::test]
18322async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18323    let (buffer_id, mut cx) = setup_indent_guides_editor(
18324        &"
18325    fn main() {
18326        let a = 1;
18327    }"
18328        .unindent(),
18329        cx,
18330    )
18331    .await;
18332
18333    cx.update_editor(|editor, window, cx| {
18334        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18335            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18336        });
18337    });
18338
18339    assert_indent_guides(
18340        0..3,
18341        vec![indent_guide(buffer_id, 1, 1, 0)],
18342        Some(vec![0]),
18343        &mut cx,
18344    );
18345}
18346
18347#[gpui::test]
18348async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18349    let (buffer_id, mut cx) = setup_indent_guides_editor(
18350        &"
18351    fn main() {
18352        if 1 == 2 {
18353            let a = 1;
18354        }
18355    }"
18356        .unindent(),
18357        cx,
18358    )
18359    .await;
18360
18361    cx.update_editor(|editor, window, cx| {
18362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18363            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18364        });
18365    });
18366
18367    assert_indent_guides(
18368        0..4,
18369        vec![
18370            indent_guide(buffer_id, 1, 3, 0),
18371            indent_guide(buffer_id, 2, 2, 1),
18372        ],
18373        Some(vec![1]),
18374        &mut cx,
18375    );
18376
18377    cx.update_editor(|editor, window, cx| {
18378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18379            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18380        });
18381    });
18382
18383    assert_indent_guides(
18384        0..4,
18385        vec![
18386            indent_guide(buffer_id, 1, 3, 0),
18387            indent_guide(buffer_id, 2, 2, 1),
18388        ],
18389        Some(vec![1]),
18390        &mut cx,
18391    );
18392
18393    cx.update_editor(|editor, window, cx| {
18394        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18395            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18396        });
18397    });
18398
18399    assert_indent_guides(
18400        0..4,
18401        vec![
18402            indent_guide(buffer_id, 1, 3, 0),
18403            indent_guide(buffer_id, 2, 2, 1),
18404        ],
18405        Some(vec![0]),
18406        &mut cx,
18407    );
18408}
18409
18410#[gpui::test]
18411async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18412    let (buffer_id, mut cx) = setup_indent_guides_editor(
18413        &"
18414    fn main() {
18415        let a = 1;
18416
18417        let b = 2;
18418    }"
18419        .unindent(),
18420        cx,
18421    )
18422    .await;
18423
18424    cx.update_editor(|editor, window, cx| {
18425        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18426            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18427        });
18428    });
18429
18430    assert_indent_guides(
18431        0..5,
18432        vec![indent_guide(buffer_id, 1, 3, 0)],
18433        Some(vec![0]),
18434        &mut cx,
18435    );
18436}
18437
18438#[gpui::test]
18439async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18440    let (buffer_id, mut cx) = setup_indent_guides_editor(
18441        &"
18442    def m:
18443        a = 1
18444        pass"
18445            .unindent(),
18446        cx,
18447    )
18448    .await;
18449
18450    cx.update_editor(|editor, window, cx| {
18451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18452            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18453        });
18454    });
18455
18456    assert_indent_guides(
18457        0..3,
18458        vec![indent_guide(buffer_id, 1, 2, 0)],
18459        Some(vec![0]),
18460        &mut cx,
18461    );
18462}
18463
18464#[gpui::test]
18465async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18466    init_test(cx, |_| {});
18467    let mut cx = EditorTestContext::new(cx).await;
18468    let text = indoc! {
18469        "
18470        impl A {
18471            fn b() {
18472                0;
18473                3;
18474                5;
18475                6;
18476                7;
18477            }
18478        }
18479        "
18480    };
18481    let base_text = indoc! {
18482        "
18483        impl A {
18484            fn b() {
18485                0;
18486                1;
18487                2;
18488                3;
18489                4;
18490            }
18491            fn c() {
18492                5;
18493                6;
18494                7;
18495            }
18496        }
18497        "
18498    };
18499
18500    cx.update_editor(|editor, window, cx| {
18501        editor.set_text(text, window, cx);
18502
18503        editor.buffer().update(cx, |multibuffer, cx| {
18504            let buffer = multibuffer.as_singleton().unwrap();
18505            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18506
18507            multibuffer.set_all_diff_hunks_expanded(cx);
18508            multibuffer.add_diff(diff, cx);
18509
18510            buffer.read(cx).remote_id()
18511        })
18512    });
18513    cx.run_until_parked();
18514
18515    cx.assert_state_with_diff(
18516        indoc! { "
18517          impl A {
18518              fn b() {
18519                  0;
18520        -         1;
18521        -         2;
18522                  3;
18523        -         4;
18524        -     }
18525        -     fn c() {
18526                  5;
18527                  6;
18528                  7;
18529              }
18530          }
18531          ˇ"
18532        }
18533        .to_string(),
18534    );
18535
18536    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18537        editor
18538            .snapshot(window, cx)
18539            .buffer_snapshot
18540            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18541            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18542            .collect::<Vec<_>>()
18543    });
18544    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18545    assert_eq!(
18546        actual_guides,
18547        vec![
18548            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18549            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18550            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18551        ]
18552    );
18553}
18554
18555#[gpui::test]
18556async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18557    init_test(cx, |_| {});
18558    let mut cx = EditorTestContext::new(cx).await;
18559
18560    let diff_base = r#"
18561        a
18562        b
18563        c
18564        "#
18565    .unindent();
18566
18567    cx.set_state(
18568        &r#"
18569        ˇA
18570        b
18571        C
18572        "#
18573        .unindent(),
18574    );
18575    cx.set_head_text(&diff_base);
18576    cx.update_editor(|editor, window, cx| {
18577        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18578    });
18579    executor.run_until_parked();
18580
18581    let both_hunks_expanded = r#"
18582        - a
18583        + ˇA
18584          b
18585        - c
18586        + C
18587        "#
18588    .unindent();
18589
18590    cx.assert_state_with_diff(both_hunks_expanded.clone());
18591
18592    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18593        let snapshot = editor.snapshot(window, cx);
18594        let hunks = editor
18595            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18596            .collect::<Vec<_>>();
18597        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18598        let buffer_id = hunks[0].buffer_id;
18599        hunks
18600            .into_iter()
18601            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18602            .collect::<Vec<_>>()
18603    });
18604    assert_eq!(hunk_ranges.len(), 2);
18605
18606    cx.update_editor(|editor, _, cx| {
18607        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18608    });
18609    executor.run_until_parked();
18610
18611    let second_hunk_expanded = r#"
18612          ˇA
18613          b
18614        - c
18615        + C
18616        "#
18617    .unindent();
18618
18619    cx.assert_state_with_diff(second_hunk_expanded);
18620
18621    cx.update_editor(|editor, _, cx| {
18622        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18623    });
18624    executor.run_until_parked();
18625
18626    cx.assert_state_with_diff(both_hunks_expanded.clone());
18627
18628    cx.update_editor(|editor, _, cx| {
18629        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18630    });
18631    executor.run_until_parked();
18632
18633    let first_hunk_expanded = r#"
18634        - a
18635        + ˇA
18636          b
18637          C
18638        "#
18639    .unindent();
18640
18641    cx.assert_state_with_diff(first_hunk_expanded);
18642
18643    cx.update_editor(|editor, _, cx| {
18644        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18645    });
18646    executor.run_until_parked();
18647
18648    cx.assert_state_with_diff(both_hunks_expanded);
18649
18650    cx.set_state(
18651        &r#"
18652        ˇA
18653        b
18654        "#
18655        .unindent(),
18656    );
18657    cx.run_until_parked();
18658
18659    // TODO this cursor position seems bad
18660    cx.assert_state_with_diff(
18661        r#"
18662        - ˇa
18663        + A
18664          b
18665        "#
18666        .unindent(),
18667    );
18668
18669    cx.update_editor(|editor, window, cx| {
18670        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18671    });
18672
18673    cx.assert_state_with_diff(
18674        r#"
18675            - ˇa
18676            + A
18677              b
18678            - c
18679            "#
18680        .unindent(),
18681    );
18682
18683    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18684        let snapshot = editor.snapshot(window, cx);
18685        let hunks = editor
18686            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18687            .collect::<Vec<_>>();
18688        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18689        let buffer_id = hunks[0].buffer_id;
18690        hunks
18691            .into_iter()
18692            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18693            .collect::<Vec<_>>()
18694    });
18695    assert_eq!(hunk_ranges.len(), 2);
18696
18697    cx.update_editor(|editor, _, cx| {
18698        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18699    });
18700    executor.run_until_parked();
18701
18702    cx.assert_state_with_diff(
18703        r#"
18704        - ˇa
18705        + A
18706          b
18707        "#
18708        .unindent(),
18709    );
18710}
18711
18712#[gpui::test]
18713async fn test_toggle_deletion_hunk_at_start_of_file(
18714    executor: BackgroundExecutor,
18715    cx: &mut TestAppContext,
18716) {
18717    init_test(cx, |_| {});
18718    let mut cx = EditorTestContext::new(cx).await;
18719
18720    let diff_base = r#"
18721        a
18722        b
18723        c
18724        "#
18725    .unindent();
18726
18727    cx.set_state(
18728        &r#"
18729        ˇb
18730        c
18731        "#
18732        .unindent(),
18733    );
18734    cx.set_head_text(&diff_base);
18735    cx.update_editor(|editor, window, cx| {
18736        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18737    });
18738    executor.run_until_parked();
18739
18740    let hunk_expanded = r#"
18741        - a
18742          ˇb
18743          c
18744        "#
18745    .unindent();
18746
18747    cx.assert_state_with_diff(hunk_expanded.clone());
18748
18749    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18750        let snapshot = editor.snapshot(window, cx);
18751        let hunks = editor
18752            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18753            .collect::<Vec<_>>();
18754        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18755        let buffer_id = hunks[0].buffer_id;
18756        hunks
18757            .into_iter()
18758            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18759            .collect::<Vec<_>>()
18760    });
18761    assert_eq!(hunk_ranges.len(), 1);
18762
18763    cx.update_editor(|editor, _, cx| {
18764        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18765    });
18766    executor.run_until_parked();
18767
18768    let hunk_collapsed = r#"
18769          ˇb
18770          c
18771        "#
18772    .unindent();
18773
18774    cx.assert_state_with_diff(hunk_collapsed);
18775
18776    cx.update_editor(|editor, _, cx| {
18777        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18778    });
18779    executor.run_until_parked();
18780
18781    cx.assert_state_with_diff(hunk_expanded.clone());
18782}
18783
18784#[gpui::test]
18785async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18786    init_test(cx, |_| {});
18787
18788    let fs = FakeFs::new(cx.executor());
18789    fs.insert_tree(
18790        path!("/test"),
18791        json!({
18792            ".git": {},
18793            "file-1": "ONE\n",
18794            "file-2": "TWO\n",
18795            "file-3": "THREE\n",
18796        }),
18797    )
18798    .await;
18799
18800    fs.set_head_for_repo(
18801        path!("/test/.git").as_ref(),
18802        &[
18803            ("file-1".into(), "one\n".into()),
18804            ("file-2".into(), "two\n".into()),
18805            ("file-3".into(), "three\n".into()),
18806        ],
18807        "deadbeef",
18808    );
18809
18810    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18811    let mut buffers = vec![];
18812    for i in 1..=3 {
18813        let buffer = project
18814            .update(cx, |project, cx| {
18815                let path = format!(path!("/test/file-{}"), i);
18816                project.open_local_buffer(path, cx)
18817            })
18818            .await
18819            .unwrap();
18820        buffers.push(buffer);
18821    }
18822
18823    let multibuffer = cx.new(|cx| {
18824        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18825        multibuffer.set_all_diff_hunks_expanded(cx);
18826        for buffer in &buffers {
18827            let snapshot = buffer.read(cx).snapshot();
18828            multibuffer.set_excerpts_for_path(
18829                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18830                buffer.clone(),
18831                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18832                DEFAULT_MULTIBUFFER_CONTEXT,
18833                cx,
18834            );
18835        }
18836        multibuffer
18837    });
18838
18839    let editor = cx.add_window(|window, cx| {
18840        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18841    });
18842    cx.run_until_parked();
18843
18844    let snapshot = editor
18845        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18846        .unwrap();
18847    let hunks = snapshot
18848        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18849        .map(|hunk| match hunk {
18850            DisplayDiffHunk::Unfolded {
18851                display_row_range, ..
18852            } => display_row_range,
18853            DisplayDiffHunk::Folded { .. } => unreachable!(),
18854        })
18855        .collect::<Vec<_>>();
18856    assert_eq!(
18857        hunks,
18858        [
18859            DisplayRow(2)..DisplayRow(4),
18860            DisplayRow(7)..DisplayRow(9),
18861            DisplayRow(12)..DisplayRow(14),
18862        ]
18863    );
18864}
18865
18866#[gpui::test]
18867async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18868    init_test(cx, |_| {});
18869
18870    let mut cx = EditorTestContext::new(cx).await;
18871    cx.set_head_text(indoc! { "
18872        one
18873        two
18874        three
18875        four
18876        five
18877        "
18878    });
18879    cx.set_index_text(indoc! { "
18880        one
18881        two
18882        three
18883        four
18884        five
18885        "
18886    });
18887    cx.set_state(indoc! {"
18888        one
18889        TWO
18890        ˇTHREE
18891        FOUR
18892        five
18893    "});
18894    cx.run_until_parked();
18895    cx.update_editor(|editor, window, cx| {
18896        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18897    });
18898    cx.run_until_parked();
18899    cx.assert_index_text(Some(indoc! {"
18900        one
18901        TWO
18902        THREE
18903        FOUR
18904        five
18905    "}));
18906    cx.set_state(indoc! { "
18907        one
18908        TWO
18909        ˇTHREE-HUNDRED
18910        FOUR
18911        five
18912    "});
18913    cx.run_until_parked();
18914    cx.update_editor(|editor, window, cx| {
18915        let snapshot = editor.snapshot(window, cx);
18916        let hunks = editor
18917            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18918            .collect::<Vec<_>>();
18919        assert_eq!(hunks.len(), 1);
18920        assert_eq!(
18921            hunks[0].status(),
18922            DiffHunkStatus {
18923                kind: DiffHunkStatusKind::Modified,
18924                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18925            }
18926        );
18927
18928        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18929    });
18930    cx.run_until_parked();
18931    cx.assert_index_text(Some(indoc! {"
18932        one
18933        TWO
18934        THREE-HUNDRED
18935        FOUR
18936        five
18937    "}));
18938}
18939
18940#[gpui::test]
18941fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18942    init_test(cx, |_| {});
18943
18944    let editor = cx.add_window(|window, cx| {
18945        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18946        build_editor(buffer, window, cx)
18947    });
18948
18949    let render_args = Arc::new(Mutex::new(None));
18950    let snapshot = editor
18951        .update(cx, |editor, window, cx| {
18952            let snapshot = editor.buffer().read(cx).snapshot(cx);
18953            let range =
18954                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18955
18956            struct RenderArgs {
18957                row: MultiBufferRow,
18958                folded: bool,
18959                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18960            }
18961
18962            let crease = Crease::inline(
18963                range,
18964                FoldPlaceholder::test(),
18965                {
18966                    let toggle_callback = render_args.clone();
18967                    move |row, folded, callback, _window, _cx| {
18968                        *toggle_callback.lock() = Some(RenderArgs {
18969                            row,
18970                            folded,
18971                            callback,
18972                        });
18973                        div()
18974                    }
18975                },
18976                |_row, _folded, _window, _cx| div(),
18977            );
18978
18979            editor.insert_creases(Some(crease), cx);
18980            let snapshot = editor.snapshot(window, cx);
18981            let _div = snapshot.render_crease_toggle(
18982                MultiBufferRow(1),
18983                false,
18984                cx.entity().clone(),
18985                window,
18986                cx,
18987            );
18988            snapshot
18989        })
18990        .unwrap();
18991
18992    let render_args = render_args.lock().take().unwrap();
18993    assert_eq!(render_args.row, MultiBufferRow(1));
18994    assert!(!render_args.folded);
18995    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18996
18997    cx.update_window(*editor, |_, window, cx| {
18998        (render_args.callback)(true, window, cx)
18999    })
19000    .unwrap();
19001    let snapshot = editor
19002        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19003        .unwrap();
19004    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19005
19006    cx.update_window(*editor, |_, window, cx| {
19007        (render_args.callback)(false, window, cx)
19008    })
19009    .unwrap();
19010    let snapshot = editor
19011        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19012        .unwrap();
19013    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19014}
19015
19016#[gpui::test]
19017async fn test_input_text(cx: &mut TestAppContext) {
19018    init_test(cx, |_| {});
19019    let mut cx = EditorTestContext::new(cx).await;
19020
19021    cx.set_state(
19022        &r#"ˇone
19023        two
19024
19025        three
19026        fourˇ
19027        five
19028
19029        siˇx"#
19030            .unindent(),
19031    );
19032
19033    cx.dispatch_action(HandleInput(String::new()));
19034    cx.assert_editor_state(
19035        &r#"ˇone
19036        two
19037
19038        three
19039        fourˇ
19040        five
19041
19042        siˇx"#
19043            .unindent(),
19044    );
19045
19046    cx.dispatch_action(HandleInput("AAAA".to_string()));
19047    cx.assert_editor_state(
19048        &r#"AAAAˇone
19049        two
19050
19051        three
19052        fourAAAAˇ
19053        five
19054
19055        siAAAAˇx"#
19056            .unindent(),
19057    );
19058}
19059
19060#[gpui::test]
19061async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19062    init_test(cx, |_| {});
19063
19064    let mut cx = EditorTestContext::new(cx).await;
19065    cx.set_state(
19066        r#"let foo = 1;
19067let foo = 2;
19068let foo = 3;
19069let fooˇ = 4;
19070let foo = 5;
19071let foo = 6;
19072let foo = 7;
19073let foo = 8;
19074let foo = 9;
19075let foo = 10;
19076let foo = 11;
19077let foo = 12;
19078let foo = 13;
19079let foo = 14;
19080let foo = 15;"#,
19081    );
19082
19083    cx.update_editor(|e, window, cx| {
19084        assert_eq!(
19085            e.next_scroll_position,
19086            NextScrollCursorCenterTopBottom::Center,
19087            "Default next scroll direction is center",
19088        );
19089
19090        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19091        assert_eq!(
19092            e.next_scroll_position,
19093            NextScrollCursorCenterTopBottom::Top,
19094            "After center, next scroll direction should be top",
19095        );
19096
19097        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19098        assert_eq!(
19099            e.next_scroll_position,
19100            NextScrollCursorCenterTopBottom::Bottom,
19101            "After top, next scroll direction should be bottom",
19102        );
19103
19104        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19105        assert_eq!(
19106            e.next_scroll_position,
19107            NextScrollCursorCenterTopBottom::Center,
19108            "After bottom, scrolling should start over",
19109        );
19110
19111        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19112        assert_eq!(
19113            e.next_scroll_position,
19114            NextScrollCursorCenterTopBottom::Top,
19115            "Scrolling continues if retriggered fast enough"
19116        );
19117    });
19118
19119    cx.executor()
19120        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19121    cx.executor().run_until_parked();
19122    cx.update_editor(|e, _, _| {
19123        assert_eq!(
19124            e.next_scroll_position,
19125            NextScrollCursorCenterTopBottom::Center,
19126            "If scrolling is not triggered fast enough, it should reset"
19127        );
19128    });
19129}
19130
19131#[gpui::test]
19132async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19133    init_test(cx, |_| {});
19134    let mut cx = EditorLspTestContext::new_rust(
19135        lsp::ServerCapabilities {
19136            definition_provider: Some(lsp::OneOf::Left(true)),
19137            references_provider: Some(lsp::OneOf::Left(true)),
19138            ..lsp::ServerCapabilities::default()
19139        },
19140        cx,
19141    )
19142    .await;
19143
19144    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19145        let go_to_definition = cx
19146            .lsp
19147            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19148                move |params, _| async move {
19149                    if empty_go_to_definition {
19150                        Ok(None)
19151                    } else {
19152                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19153                            uri: params.text_document_position_params.text_document.uri,
19154                            range: lsp::Range::new(
19155                                lsp::Position::new(4, 3),
19156                                lsp::Position::new(4, 6),
19157                            ),
19158                        })))
19159                    }
19160                },
19161            );
19162        let references = cx
19163            .lsp
19164            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19165                Ok(Some(vec![lsp::Location {
19166                    uri: params.text_document_position.text_document.uri,
19167                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19168                }]))
19169            });
19170        (go_to_definition, references)
19171    };
19172
19173    cx.set_state(
19174        &r#"fn one() {
19175            let mut a = ˇtwo();
19176        }
19177
19178        fn two() {}"#
19179            .unindent(),
19180    );
19181    set_up_lsp_handlers(false, &mut cx);
19182    let navigated = cx
19183        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19184        .await
19185        .expect("Failed to navigate to definition");
19186    assert_eq!(
19187        navigated,
19188        Navigated::Yes,
19189        "Should have navigated to definition from the GetDefinition response"
19190    );
19191    cx.assert_editor_state(
19192        &r#"fn one() {
19193            let mut a = two();
19194        }
19195
19196        fn «twoˇ»() {}"#
19197            .unindent(),
19198    );
19199
19200    let editors = cx.update_workspace(|workspace, _, cx| {
19201        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19202    });
19203    cx.update_editor(|_, _, test_editor_cx| {
19204        assert_eq!(
19205            editors.len(),
19206            1,
19207            "Initially, only one, test, editor should be open in the workspace"
19208        );
19209        assert_eq!(
19210            test_editor_cx.entity(),
19211            editors.last().expect("Asserted len is 1").clone()
19212        );
19213    });
19214
19215    set_up_lsp_handlers(true, &mut cx);
19216    let navigated = cx
19217        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19218        .await
19219        .expect("Failed to navigate to lookup references");
19220    assert_eq!(
19221        navigated,
19222        Navigated::Yes,
19223        "Should have navigated to references as a fallback after empty GoToDefinition response"
19224    );
19225    // We should not change the selections in the existing file,
19226    // if opening another milti buffer with the references
19227    cx.assert_editor_state(
19228        &r#"fn one() {
19229            let mut a = two();
19230        }
19231
19232        fn «twoˇ»() {}"#
19233            .unindent(),
19234    );
19235    let editors = cx.update_workspace(|workspace, _, cx| {
19236        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19237    });
19238    cx.update_editor(|_, _, test_editor_cx| {
19239        assert_eq!(
19240            editors.len(),
19241            2,
19242            "After falling back to references search, we open a new editor with the results"
19243        );
19244        let references_fallback_text = editors
19245            .into_iter()
19246            .find(|new_editor| *new_editor != test_editor_cx.entity())
19247            .expect("Should have one non-test editor now")
19248            .read(test_editor_cx)
19249            .text(test_editor_cx);
19250        assert_eq!(
19251            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19252            "Should use the range from the references response and not the GoToDefinition one"
19253        );
19254    });
19255}
19256
19257#[gpui::test]
19258async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19259    init_test(cx, |_| {});
19260    cx.update(|cx| {
19261        let mut editor_settings = EditorSettings::get_global(cx).clone();
19262        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19263        EditorSettings::override_global(editor_settings, cx);
19264    });
19265    let mut cx = EditorLspTestContext::new_rust(
19266        lsp::ServerCapabilities {
19267            definition_provider: Some(lsp::OneOf::Left(true)),
19268            references_provider: Some(lsp::OneOf::Left(true)),
19269            ..lsp::ServerCapabilities::default()
19270        },
19271        cx,
19272    )
19273    .await;
19274    let original_state = r#"fn one() {
19275        let mut a = ˇtwo();
19276    }
19277
19278    fn two() {}"#
19279        .unindent();
19280    cx.set_state(&original_state);
19281
19282    let mut go_to_definition = cx
19283        .lsp
19284        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19285            move |_, _| async move { Ok(None) },
19286        );
19287    let _references = cx
19288        .lsp
19289        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19290            panic!("Should not call for references with no go to definition fallback")
19291        });
19292
19293    let navigated = cx
19294        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19295        .await
19296        .expect("Failed to navigate to lookup references");
19297    go_to_definition
19298        .next()
19299        .await
19300        .expect("Should have called the go_to_definition handler");
19301
19302    assert_eq!(
19303        navigated,
19304        Navigated::No,
19305        "Should have navigated to references as a fallback after empty GoToDefinition response"
19306    );
19307    cx.assert_editor_state(&original_state);
19308    let editors = cx.update_workspace(|workspace, _, cx| {
19309        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19310    });
19311    cx.update_editor(|_, _, _| {
19312        assert_eq!(
19313            editors.len(),
19314            1,
19315            "After unsuccessful fallback, no other editor should have been opened"
19316        );
19317    });
19318}
19319
19320#[gpui::test]
19321async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19322    init_test(cx, |_| {});
19323
19324    let language = Arc::new(Language::new(
19325        LanguageConfig::default(),
19326        Some(tree_sitter_rust::LANGUAGE.into()),
19327    ));
19328
19329    let text = r#"
19330        #[cfg(test)]
19331        mod tests() {
19332            #[test]
19333            fn runnable_1() {
19334                let a = 1;
19335            }
19336
19337            #[test]
19338            fn runnable_2() {
19339                let a = 1;
19340                let b = 2;
19341            }
19342        }
19343    "#
19344    .unindent();
19345
19346    let fs = FakeFs::new(cx.executor());
19347    fs.insert_file("/file.rs", Default::default()).await;
19348
19349    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19350    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19351    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19352    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19353    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19354
19355    let editor = cx.new_window_entity(|window, cx| {
19356        Editor::new(
19357            EditorMode::full(),
19358            multi_buffer,
19359            Some(project.clone()),
19360            window,
19361            cx,
19362        )
19363    });
19364
19365    editor.update_in(cx, |editor, window, cx| {
19366        let snapshot = editor.buffer().read(cx).snapshot(cx);
19367        editor.tasks.insert(
19368            (buffer.read(cx).remote_id(), 3),
19369            RunnableTasks {
19370                templates: vec![],
19371                offset: snapshot.anchor_before(43),
19372                column: 0,
19373                extra_variables: HashMap::default(),
19374                context_range: BufferOffset(43)..BufferOffset(85),
19375            },
19376        );
19377        editor.tasks.insert(
19378            (buffer.read(cx).remote_id(), 8),
19379            RunnableTasks {
19380                templates: vec![],
19381                offset: snapshot.anchor_before(86),
19382                column: 0,
19383                extra_variables: HashMap::default(),
19384                context_range: BufferOffset(86)..BufferOffset(191),
19385            },
19386        );
19387
19388        // Test finding task when cursor is inside function body
19389        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19390            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19391        });
19392        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19393        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19394
19395        // Test finding task when cursor is on function name
19396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19397            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19398        });
19399        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19400        assert_eq!(row, 8, "Should find task when cursor is on function name");
19401    });
19402}
19403
19404#[gpui::test]
19405async fn test_folding_buffers(cx: &mut TestAppContext) {
19406    init_test(cx, |_| {});
19407
19408    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19409    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19410    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19411
19412    let fs = FakeFs::new(cx.executor());
19413    fs.insert_tree(
19414        path!("/a"),
19415        json!({
19416            "first.rs": sample_text_1,
19417            "second.rs": sample_text_2,
19418            "third.rs": sample_text_3,
19419        }),
19420    )
19421    .await;
19422    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19423    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19424    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19425    let worktree = project.update(cx, |project, cx| {
19426        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19427        assert_eq!(worktrees.len(), 1);
19428        worktrees.pop().unwrap()
19429    });
19430    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19431
19432    let buffer_1 = project
19433        .update(cx, |project, cx| {
19434            project.open_buffer((worktree_id, "first.rs"), cx)
19435        })
19436        .await
19437        .unwrap();
19438    let buffer_2 = project
19439        .update(cx, |project, cx| {
19440            project.open_buffer((worktree_id, "second.rs"), cx)
19441        })
19442        .await
19443        .unwrap();
19444    let buffer_3 = project
19445        .update(cx, |project, cx| {
19446            project.open_buffer((worktree_id, "third.rs"), cx)
19447        })
19448        .await
19449        .unwrap();
19450
19451    let multi_buffer = cx.new(|cx| {
19452        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19453        multi_buffer.push_excerpts(
19454            buffer_1.clone(),
19455            [
19456                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19457                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19458                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19459            ],
19460            cx,
19461        );
19462        multi_buffer.push_excerpts(
19463            buffer_2.clone(),
19464            [
19465                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19466                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19467                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19468            ],
19469            cx,
19470        );
19471        multi_buffer.push_excerpts(
19472            buffer_3.clone(),
19473            [
19474                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19475                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19476                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19477            ],
19478            cx,
19479        );
19480        multi_buffer
19481    });
19482    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19483        Editor::new(
19484            EditorMode::full(),
19485            multi_buffer.clone(),
19486            Some(project.clone()),
19487            window,
19488            cx,
19489        )
19490    });
19491
19492    assert_eq!(
19493        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19494        "\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",
19495    );
19496
19497    multi_buffer_editor.update(cx, |editor, cx| {
19498        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19499    });
19500    assert_eq!(
19501        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19502        "\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",
19503        "After folding the first buffer, its text should not be displayed"
19504    );
19505
19506    multi_buffer_editor.update(cx, |editor, cx| {
19507        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19508    });
19509    assert_eq!(
19510        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19511        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19512        "After folding the second buffer, its text should not be displayed"
19513    );
19514
19515    multi_buffer_editor.update(cx, |editor, cx| {
19516        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19517    });
19518    assert_eq!(
19519        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19520        "\n\n\n\n\n",
19521        "After folding the third buffer, its text should not be displayed"
19522    );
19523
19524    // Emulate selection inside the fold logic, that should work
19525    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19526        editor
19527            .snapshot(window, cx)
19528            .next_line_boundary(Point::new(0, 4));
19529    });
19530
19531    multi_buffer_editor.update(cx, |editor, cx| {
19532        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19533    });
19534    assert_eq!(
19535        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19536        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19537        "After unfolding the second buffer, its text should be displayed"
19538    );
19539
19540    // Typing inside of buffer 1 causes that buffer to be unfolded.
19541    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19542        assert_eq!(
19543            multi_buffer
19544                .read(cx)
19545                .snapshot(cx)
19546                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19547                .collect::<String>(),
19548            "bbbb"
19549        );
19550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19551            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19552        });
19553        editor.handle_input("B", window, cx);
19554    });
19555
19556    assert_eq!(
19557        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19558        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19559        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19560    );
19561
19562    multi_buffer_editor.update(cx, |editor, cx| {
19563        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19564    });
19565    assert_eq!(
19566        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19567        "\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",
19568        "After unfolding the all buffers, all original text should be displayed"
19569    );
19570}
19571
19572#[gpui::test]
19573async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19574    init_test(cx, |_| {});
19575
19576    let sample_text_1 = "1111\n2222\n3333".to_string();
19577    let sample_text_2 = "4444\n5555\n6666".to_string();
19578    let sample_text_3 = "7777\n8888\n9999".to_string();
19579
19580    let fs = FakeFs::new(cx.executor());
19581    fs.insert_tree(
19582        path!("/a"),
19583        json!({
19584            "first.rs": sample_text_1,
19585            "second.rs": sample_text_2,
19586            "third.rs": sample_text_3,
19587        }),
19588    )
19589    .await;
19590    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19591    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19592    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19593    let worktree = project.update(cx, |project, cx| {
19594        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19595        assert_eq!(worktrees.len(), 1);
19596        worktrees.pop().unwrap()
19597    });
19598    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19599
19600    let buffer_1 = project
19601        .update(cx, |project, cx| {
19602            project.open_buffer((worktree_id, "first.rs"), cx)
19603        })
19604        .await
19605        .unwrap();
19606    let buffer_2 = project
19607        .update(cx, |project, cx| {
19608            project.open_buffer((worktree_id, "second.rs"), cx)
19609        })
19610        .await
19611        .unwrap();
19612    let buffer_3 = project
19613        .update(cx, |project, cx| {
19614            project.open_buffer((worktree_id, "third.rs"), cx)
19615        })
19616        .await
19617        .unwrap();
19618
19619    let multi_buffer = cx.new(|cx| {
19620        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19621        multi_buffer.push_excerpts(
19622            buffer_1.clone(),
19623            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19624            cx,
19625        );
19626        multi_buffer.push_excerpts(
19627            buffer_2.clone(),
19628            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19629            cx,
19630        );
19631        multi_buffer.push_excerpts(
19632            buffer_3.clone(),
19633            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19634            cx,
19635        );
19636        multi_buffer
19637    });
19638
19639    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19640        Editor::new(
19641            EditorMode::full(),
19642            multi_buffer,
19643            Some(project.clone()),
19644            window,
19645            cx,
19646        )
19647    });
19648
19649    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19650    assert_eq!(
19651        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19652        full_text,
19653    );
19654
19655    multi_buffer_editor.update(cx, |editor, cx| {
19656        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19657    });
19658    assert_eq!(
19659        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19660        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19661        "After folding the first buffer, its text should not be displayed"
19662    );
19663
19664    multi_buffer_editor.update(cx, |editor, cx| {
19665        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19666    });
19667
19668    assert_eq!(
19669        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19670        "\n\n\n\n\n\n7777\n8888\n9999",
19671        "After folding the second buffer, its text should not be displayed"
19672    );
19673
19674    multi_buffer_editor.update(cx, |editor, cx| {
19675        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19676    });
19677    assert_eq!(
19678        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19679        "\n\n\n\n\n",
19680        "After folding the third buffer, its text should not be displayed"
19681    );
19682
19683    multi_buffer_editor.update(cx, |editor, cx| {
19684        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19685    });
19686    assert_eq!(
19687        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19688        "\n\n\n\n4444\n5555\n6666\n\n",
19689        "After unfolding the second buffer, its text should be displayed"
19690    );
19691
19692    multi_buffer_editor.update(cx, |editor, cx| {
19693        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19694    });
19695    assert_eq!(
19696        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19697        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19698        "After unfolding the first buffer, its text should be displayed"
19699    );
19700
19701    multi_buffer_editor.update(cx, |editor, cx| {
19702        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19703    });
19704    assert_eq!(
19705        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19706        full_text,
19707        "After unfolding all buffers, all original text should be displayed"
19708    );
19709}
19710
19711#[gpui::test]
19712async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19713    init_test(cx, |_| {});
19714
19715    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19716
19717    let fs = FakeFs::new(cx.executor());
19718    fs.insert_tree(
19719        path!("/a"),
19720        json!({
19721            "main.rs": sample_text,
19722        }),
19723    )
19724    .await;
19725    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19726    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19727    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19728    let worktree = project.update(cx, |project, cx| {
19729        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19730        assert_eq!(worktrees.len(), 1);
19731        worktrees.pop().unwrap()
19732    });
19733    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19734
19735    let buffer_1 = project
19736        .update(cx, |project, cx| {
19737            project.open_buffer((worktree_id, "main.rs"), cx)
19738        })
19739        .await
19740        .unwrap();
19741
19742    let multi_buffer = cx.new(|cx| {
19743        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19744        multi_buffer.push_excerpts(
19745            buffer_1.clone(),
19746            [ExcerptRange::new(
19747                Point::new(0, 0)
19748                    ..Point::new(
19749                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19750                        0,
19751                    ),
19752            )],
19753            cx,
19754        );
19755        multi_buffer
19756    });
19757    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19758        Editor::new(
19759            EditorMode::full(),
19760            multi_buffer,
19761            Some(project.clone()),
19762            window,
19763            cx,
19764        )
19765    });
19766
19767    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19768    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19769        enum TestHighlight {}
19770        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19771        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19772        editor.highlight_text::<TestHighlight>(
19773            vec![highlight_range.clone()],
19774            HighlightStyle::color(Hsla::green()),
19775            cx,
19776        );
19777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19778            s.select_ranges(Some(highlight_range))
19779        });
19780    });
19781
19782    let full_text = format!("\n\n{sample_text}");
19783    assert_eq!(
19784        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19785        full_text,
19786    );
19787}
19788
19789#[gpui::test]
19790async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19791    init_test(cx, |_| {});
19792    cx.update(|cx| {
19793        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19794            "keymaps/default-linux.json",
19795            cx,
19796        )
19797        .unwrap();
19798        cx.bind_keys(default_key_bindings);
19799    });
19800
19801    let (editor, cx) = cx.add_window_view(|window, cx| {
19802        let multi_buffer = MultiBuffer::build_multi(
19803            [
19804                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19805                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19806                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19807                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19808            ],
19809            cx,
19810        );
19811        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19812
19813        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19814        // fold all but the second buffer, so that we test navigating between two
19815        // adjacent folded buffers, as well as folded buffers at the start and
19816        // end the multibuffer
19817        editor.fold_buffer(buffer_ids[0], cx);
19818        editor.fold_buffer(buffer_ids[2], cx);
19819        editor.fold_buffer(buffer_ids[3], cx);
19820
19821        editor
19822    });
19823    cx.simulate_resize(size(px(1000.), px(1000.)));
19824
19825    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19826    cx.assert_excerpts_with_selections(indoc! {"
19827        [EXCERPT]
19828        ˇ[FOLDED]
19829        [EXCERPT]
19830        a1
19831        b1
19832        [EXCERPT]
19833        [FOLDED]
19834        [EXCERPT]
19835        [FOLDED]
19836        "
19837    });
19838    cx.simulate_keystroke("down");
19839    cx.assert_excerpts_with_selections(indoc! {"
19840        [EXCERPT]
19841        [FOLDED]
19842        [EXCERPT]
19843        ˇa1
19844        b1
19845        [EXCERPT]
19846        [FOLDED]
19847        [EXCERPT]
19848        [FOLDED]
19849        "
19850    });
19851    cx.simulate_keystroke("down");
19852    cx.assert_excerpts_with_selections(indoc! {"
19853        [EXCERPT]
19854        [FOLDED]
19855        [EXCERPT]
19856        a1
19857        ˇb1
19858        [EXCERPT]
19859        [FOLDED]
19860        [EXCERPT]
19861        [FOLDED]
19862        "
19863    });
19864    cx.simulate_keystroke("down");
19865    cx.assert_excerpts_with_selections(indoc! {"
19866        [EXCERPT]
19867        [FOLDED]
19868        [EXCERPT]
19869        a1
19870        b1
19871        ˇ[EXCERPT]
19872        [FOLDED]
19873        [EXCERPT]
19874        [FOLDED]
19875        "
19876    });
19877    cx.simulate_keystroke("down");
19878    cx.assert_excerpts_with_selections(indoc! {"
19879        [EXCERPT]
19880        [FOLDED]
19881        [EXCERPT]
19882        a1
19883        b1
19884        [EXCERPT]
19885        ˇ[FOLDED]
19886        [EXCERPT]
19887        [FOLDED]
19888        "
19889    });
19890    for _ in 0..5 {
19891        cx.simulate_keystroke("down");
19892        cx.assert_excerpts_with_selections(indoc! {"
19893            [EXCERPT]
19894            [FOLDED]
19895            [EXCERPT]
19896            a1
19897            b1
19898            [EXCERPT]
19899            [FOLDED]
19900            [EXCERPT]
19901            ˇ[FOLDED]
19902            "
19903        });
19904    }
19905
19906    cx.simulate_keystroke("up");
19907    cx.assert_excerpts_with_selections(indoc! {"
19908        [EXCERPT]
19909        [FOLDED]
19910        [EXCERPT]
19911        a1
19912        b1
19913        [EXCERPT]
19914        ˇ[FOLDED]
19915        [EXCERPT]
19916        [FOLDED]
19917        "
19918    });
19919    cx.simulate_keystroke("up");
19920    cx.assert_excerpts_with_selections(indoc! {"
19921        [EXCERPT]
19922        [FOLDED]
19923        [EXCERPT]
19924        a1
19925        b1
19926        ˇ[EXCERPT]
19927        [FOLDED]
19928        [EXCERPT]
19929        [FOLDED]
19930        "
19931    });
19932    cx.simulate_keystroke("up");
19933    cx.assert_excerpts_with_selections(indoc! {"
19934        [EXCERPT]
19935        [FOLDED]
19936        [EXCERPT]
19937        a1
19938        ˇb1
19939        [EXCERPT]
19940        [FOLDED]
19941        [EXCERPT]
19942        [FOLDED]
19943        "
19944    });
19945    cx.simulate_keystroke("up");
19946    cx.assert_excerpts_with_selections(indoc! {"
19947        [EXCERPT]
19948        [FOLDED]
19949        [EXCERPT]
19950        ˇa1
19951        b1
19952        [EXCERPT]
19953        [FOLDED]
19954        [EXCERPT]
19955        [FOLDED]
19956        "
19957    });
19958    for _ in 0..5 {
19959        cx.simulate_keystroke("up");
19960        cx.assert_excerpts_with_selections(indoc! {"
19961            [EXCERPT]
19962            ˇ[FOLDED]
19963            [EXCERPT]
19964            a1
19965            b1
19966            [EXCERPT]
19967            [FOLDED]
19968            [EXCERPT]
19969            [FOLDED]
19970            "
19971        });
19972    }
19973}
19974
19975#[gpui::test]
19976async fn test_inline_completion_text(cx: &mut TestAppContext) {
19977    init_test(cx, |_| {});
19978
19979    // Simple insertion
19980    assert_highlighted_edits(
19981        "Hello, world!",
19982        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19983        true,
19984        cx,
19985        |highlighted_edits, cx| {
19986            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19987            assert_eq!(highlighted_edits.highlights.len(), 1);
19988            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19989            assert_eq!(
19990                highlighted_edits.highlights[0].1.background_color,
19991                Some(cx.theme().status().created_background)
19992            );
19993        },
19994    )
19995    .await;
19996
19997    // Replacement
19998    assert_highlighted_edits(
19999        "This is a test.",
20000        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20001        false,
20002        cx,
20003        |highlighted_edits, cx| {
20004            assert_eq!(highlighted_edits.text, "That is a test.");
20005            assert_eq!(highlighted_edits.highlights.len(), 1);
20006            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20007            assert_eq!(
20008                highlighted_edits.highlights[0].1.background_color,
20009                Some(cx.theme().status().created_background)
20010            );
20011        },
20012    )
20013    .await;
20014
20015    // Multiple edits
20016    assert_highlighted_edits(
20017        "Hello, world!",
20018        vec![
20019            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20020            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20021        ],
20022        false,
20023        cx,
20024        |highlighted_edits, cx| {
20025            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20026            assert_eq!(highlighted_edits.highlights.len(), 2);
20027            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20028            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20029            assert_eq!(
20030                highlighted_edits.highlights[0].1.background_color,
20031                Some(cx.theme().status().created_background)
20032            );
20033            assert_eq!(
20034                highlighted_edits.highlights[1].1.background_color,
20035                Some(cx.theme().status().created_background)
20036            );
20037        },
20038    )
20039    .await;
20040
20041    // Multiple lines with edits
20042    assert_highlighted_edits(
20043        "First line\nSecond line\nThird line\nFourth line",
20044        vec![
20045            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20046            (
20047                Point::new(2, 0)..Point::new(2, 10),
20048                "New third line".to_string(),
20049            ),
20050            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20051        ],
20052        false,
20053        cx,
20054        |highlighted_edits, cx| {
20055            assert_eq!(
20056                highlighted_edits.text,
20057                "Second modified\nNew third line\nFourth updated line"
20058            );
20059            assert_eq!(highlighted_edits.highlights.len(), 3);
20060            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20061            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20062            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20063            for highlight in &highlighted_edits.highlights {
20064                assert_eq!(
20065                    highlight.1.background_color,
20066                    Some(cx.theme().status().created_background)
20067                );
20068            }
20069        },
20070    )
20071    .await;
20072}
20073
20074#[gpui::test]
20075async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20076    init_test(cx, |_| {});
20077
20078    // Deletion
20079    assert_highlighted_edits(
20080        "Hello, world!",
20081        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20082        true,
20083        cx,
20084        |highlighted_edits, cx| {
20085            assert_eq!(highlighted_edits.text, "Hello, world!");
20086            assert_eq!(highlighted_edits.highlights.len(), 1);
20087            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20088            assert_eq!(
20089                highlighted_edits.highlights[0].1.background_color,
20090                Some(cx.theme().status().deleted_background)
20091            );
20092        },
20093    )
20094    .await;
20095
20096    // Insertion
20097    assert_highlighted_edits(
20098        "Hello, world!",
20099        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20100        true,
20101        cx,
20102        |highlighted_edits, cx| {
20103            assert_eq!(highlighted_edits.highlights.len(), 1);
20104            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20105            assert_eq!(
20106                highlighted_edits.highlights[0].1.background_color,
20107                Some(cx.theme().status().created_background)
20108            );
20109        },
20110    )
20111    .await;
20112}
20113
20114async fn assert_highlighted_edits(
20115    text: &str,
20116    edits: Vec<(Range<Point>, String)>,
20117    include_deletions: bool,
20118    cx: &mut TestAppContext,
20119    assertion_fn: impl Fn(HighlightedText, &App),
20120) {
20121    let window = cx.add_window(|window, cx| {
20122        let buffer = MultiBuffer::build_simple(text, cx);
20123        Editor::new(EditorMode::full(), buffer, None, window, cx)
20124    });
20125    let cx = &mut VisualTestContext::from_window(*window, cx);
20126
20127    let (buffer, snapshot) = window
20128        .update(cx, |editor, _window, cx| {
20129            (
20130                editor.buffer().clone(),
20131                editor.buffer().read(cx).snapshot(cx),
20132            )
20133        })
20134        .unwrap();
20135
20136    let edits = edits
20137        .into_iter()
20138        .map(|(range, edit)| {
20139            (
20140                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20141                edit,
20142            )
20143        })
20144        .collect::<Vec<_>>();
20145
20146    let text_anchor_edits = edits
20147        .clone()
20148        .into_iter()
20149        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20150        .collect::<Vec<_>>();
20151
20152    let edit_preview = window
20153        .update(cx, |_, _window, cx| {
20154            buffer
20155                .read(cx)
20156                .as_singleton()
20157                .unwrap()
20158                .read(cx)
20159                .preview_edits(text_anchor_edits.into(), cx)
20160        })
20161        .unwrap()
20162        .await;
20163
20164    cx.update(|_window, cx| {
20165        let highlighted_edits = inline_completion_edit_text(
20166            &snapshot.as_singleton().unwrap().2,
20167            &edits,
20168            &edit_preview,
20169            include_deletions,
20170            cx,
20171        );
20172        assertion_fn(highlighted_edits, cx)
20173    });
20174}
20175
20176#[track_caller]
20177fn assert_breakpoint(
20178    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20179    path: &Arc<Path>,
20180    expected: Vec<(u32, Breakpoint)>,
20181) {
20182    if expected.len() == 0usize {
20183        assert!(!breakpoints.contains_key(path), "{}", path.display());
20184    } else {
20185        let mut breakpoint = breakpoints
20186            .get(path)
20187            .unwrap()
20188            .into_iter()
20189            .map(|breakpoint| {
20190                (
20191                    breakpoint.row,
20192                    Breakpoint {
20193                        message: breakpoint.message.clone(),
20194                        state: breakpoint.state,
20195                        condition: breakpoint.condition.clone(),
20196                        hit_condition: breakpoint.hit_condition.clone(),
20197                    },
20198                )
20199            })
20200            .collect::<Vec<_>>();
20201
20202        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20203
20204        assert_eq!(expected, breakpoint);
20205    }
20206}
20207
20208fn add_log_breakpoint_at_cursor(
20209    editor: &mut Editor,
20210    log_message: &str,
20211    window: &mut Window,
20212    cx: &mut Context<Editor>,
20213) {
20214    let (anchor, bp) = editor
20215        .breakpoints_at_cursors(window, cx)
20216        .first()
20217        .and_then(|(anchor, bp)| {
20218            if let Some(bp) = bp {
20219                Some((*anchor, bp.clone()))
20220            } else {
20221                None
20222            }
20223        })
20224        .unwrap_or_else(|| {
20225            let cursor_position: Point = editor.selections.newest(cx).head();
20226
20227            let breakpoint_position = editor
20228                .snapshot(window, cx)
20229                .display_snapshot
20230                .buffer_snapshot
20231                .anchor_before(Point::new(cursor_position.row, 0));
20232
20233            (breakpoint_position, Breakpoint::new_log(&log_message))
20234        });
20235
20236    editor.edit_breakpoint_at_anchor(
20237        anchor,
20238        bp,
20239        BreakpointEditAction::EditLogMessage(log_message.into()),
20240        cx,
20241    );
20242}
20243
20244#[gpui::test]
20245async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20246    init_test(cx, |_| {});
20247
20248    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20249    let fs = FakeFs::new(cx.executor());
20250    fs.insert_tree(
20251        path!("/a"),
20252        json!({
20253            "main.rs": sample_text,
20254        }),
20255    )
20256    .await;
20257    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20258    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20259    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20260
20261    let fs = FakeFs::new(cx.executor());
20262    fs.insert_tree(
20263        path!("/a"),
20264        json!({
20265            "main.rs": sample_text,
20266        }),
20267    )
20268    .await;
20269    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20270    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20271    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20272    let worktree_id = workspace
20273        .update(cx, |workspace, _window, cx| {
20274            workspace.project().update(cx, |project, cx| {
20275                project.worktrees(cx).next().unwrap().read(cx).id()
20276            })
20277        })
20278        .unwrap();
20279
20280    let buffer = project
20281        .update(cx, |project, cx| {
20282            project.open_buffer((worktree_id, "main.rs"), cx)
20283        })
20284        .await
20285        .unwrap();
20286
20287    let (editor, cx) = cx.add_window_view(|window, cx| {
20288        Editor::new(
20289            EditorMode::full(),
20290            MultiBuffer::build_from_buffer(buffer, cx),
20291            Some(project.clone()),
20292            window,
20293            cx,
20294        )
20295    });
20296
20297    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20298    let abs_path = project.read_with(cx, |project, cx| {
20299        project
20300            .absolute_path(&project_path, cx)
20301            .map(|path_buf| Arc::from(path_buf.to_owned()))
20302            .unwrap()
20303    });
20304
20305    // assert we can add breakpoint on the first line
20306    editor.update_in(cx, |editor, window, cx| {
20307        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20308        editor.move_to_end(&MoveToEnd, window, cx);
20309        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20310    });
20311
20312    let breakpoints = editor.update(cx, |editor, cx| {
20313        editor
20314            .breakpoint_store()
20315            .as_ref()
20316            .unwrap()
20317            .read(cx)
20318            .all_source_breakpoints(cx)
20319            .clone()
20320    });
20321
20322    assert_eq!(1, breakpoints.len());
20323    assert_breakpoint(
20324        &breakpoints,
20325        &abs_path,
20326        vec![
20327            (0, Breakpoint::new_standard()),
20328            (3, Breakpoint::new_standard()),
20329        ],
20330    );
20331
20332    editor.update_in(cx, |editor, window, cx| {
20333        editor.move_to_beginning(&MoveToBeginning, window, cx);
20334        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20335    });
20336
20337    let breakpoints = editor.update(cx, |editor, cx| {
20338        editor
20339            .breakpoint_store()
20340            .as_ref()
20341            .unwrap()
20342            .read(cx)
20343            .all_source_breakpoints(cx)
20344            .clone()
20345    });
20346
20347    assert_eq!(1, breakpoints.len());
20348    assert_breakpoint(
20349        &breakpoints,
20350        &abs_path,
20351        vec![(3, Breakpoint::new_standard())],
20352    );
20353
20354    editor.update_in(cx, |editor, window, cx| {
20355        editor.move_to_end(&MoveToEnd, window, cx);
20356        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20357    });
20358
20359    let breakpoints = editor.update(cx, |editor, cx| {
20360        editor
20361            .breakpoint_store()
20362            .as_ref()
20363            .unwrap()
20364            .read(cx)
20365            .all_source_breakpoints(cx)
20366            .clone()
20367    });
20368
20369    assert_eq!(0, breakpoints.len());
20370    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20371}
20372
20373#[gpui::test]
20374async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20375    init_test(cx, |_| {});
20376
20377    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20378
20379    let fs = FakeFs::new(cx.executor());
20380    fs.insert_tree(
20381        path!("/a"),
20382        json!({
20383            "main.rs": sample_text,
20384        }),
20385    )
20386    .await;
20387    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20388    let (workspace, cx) =
20389        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20390
20391    let worktree_id = workspace.update(cx, |workspace, cx| {
20392        workspace.project().update(cx, |project, cx| {
20393            project.worktrees(cx).next().unwrap().read(cx).id()
20394        })
20395    });
20396
20397    let buffer = project
20398        .update(cx, |project, cx| {
20399            project.open_buffer((worktree_id, "main.rs"), cx)
20400        })
20401        .await
20402        .unwrap();
20403
20404    let (editor, cx) = cx.add_window_view(|window, cx| {
20405        Editor::new(
20406            EditorMode::full(),
20407            MultiBuffer::build_from_buffer(buffer, cx),
20408            Some(project.clone()),
20409            window,
20410            cx,
20411        )
20412    });
20413
20414    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20415    let abs_path = project.read_with(cx, |project, cx| {
20416        project
20417            .absolute_path(&project_path, cx)
20418            .map(|path_buf| Arc::from(path_buf.to_owned()))
20419            .unwrap()
20420    });
20421
20422    editor.update_in(cx, |editor, window, cx| {
20423        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20424    });
20425
20426    let breakpoints = editor.update(cx, |editor, cx| {
20427        editor
20428            .breakpoint_store()
20429            .as_ref()
20430            .unwrap()
20431            .read(cx)
20432            .all_source_breakpoints(cx)
20433            .clone()
20434    });
20435
20436    assert_breakpoint(
20437        &breakpoints,
20438        &abs_path,
20439        vec![(0, Breakpoint::new_log("hello world"))],
20440    );
20441
20442    // Removing a log message from a log breakpoint should remove it
20443    editor.update_in(cx, |editor, window, cx| {
20444        add_log_breakpoint_at_cursor(editor, "", window, cx);
20445    });
20446
20447    let breakpoints = editor.update(cx, |editor, cx| {
20448        editor
20449            .breakpoint_store()
20450            .as_ref()
20451            .unwrap()
20452            .read(cx)
20453            .all_source_breakpoints(cx)
20454            .clone()
20455    });
20456
20457    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20458
20459    editor.update_in(cx, |editor, window, cx| {
20460        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20461        editor.move_to_end(&MoveToEnd, window, cx);
20462        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20463        // Not adding a log message to a standard breakpoint shouldn't remove it
20464        add_log_breakpoint_at_cursor(editor, "", window, cx);
20465    });
20466
20467    let breakpoints = editor.update(cx, |editor, cx| {
20468        editor
20469            .breakpoint_store()
20470            .as_ref()
20471            .unwrap()
20472            .read(cx)
20473            .all_source_breakpoints(cx)
20474            .clone()
20475    });
20476
20477    assert_breakpoint(
20478        &breakpoints,
20479        &abs_path,
20480        vec![
20481            (0, Breakpoint::new_standard()),
20482            (3, Breakpoint::new_standard()),
20483        ],
20484    );
20485
20486    editor.update_in(cx, |editor, window, cx| {
20487        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20488    });
20489
20490    let breakpoints = editor.update(cx, |editor, cx| {
20491        editor
20492            .breakpoint_store()
20493            .as_ref()
20494            .unwrap()
20495            .read(cx)
20496            .all_source_breakpoints(cx)
20497            .clone()
20498    });
20499
20500    assert_breakpoint(
20501        &breakpoints,
20502        &abs_path,
20503        vec![
20504            (0, Breakpoint::new_standard()),
20505            (3, Breakpoint::new_log("hello world")),
20506        ],
20507    );
20508
20509    editor.update_in(cx, |editor, window, cx| {
20510        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20511    });
20512
20513    let breakpoints = editor.update(cx, |editor, cx| {
20514        editor
20515            .breakpoint_store()
20516            .as_ref()
20517            .unwrap()
20518            .read(cx)
20519            .all_source_breakpoints(cx)
20520            .clone()
20521    });
20522
20523    assert_breakpoint(
20524        &breakpoints,
20525        &abs_path,
20526        vec![
20527            (0, Breakpoint::new_standard()),
20528            (3, Breakpoint::new_log("hello Earth!!")),
20529        ],
20530    );
20531}
20532
20533/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20534/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20535/// or when breakpoints were placed out of order. This tests for a regression too
20536#[gpui::test]
20537async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20538    init_test(cx, |_| {});
20539
20540    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20541    let fs = FakeFs::new(cx.executor());
20542    fs.insert_tree(
20543        path!("/a"),
20544        json!({
20545            "main.rs": sample_text,
20546        }),
20547    )
20548    .await;
20549    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20550    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20551    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20552
20553    let fs = FakeFs::new(cx.executor());
20554    fs.insert_tree(
20555        path!("/a"),
20556        json!({
20557            "main.rs": sample_text,
20558        }),
20559    )
20560    .await;
20561    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20562    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20563    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20564    let worktree_id = workspace
20565        .update(cx, |workspace, _window, cx| {
20566            workspace.project().update(cx, |project, cx| {
20567                project.worktrees(cx).next().unwrap().read(cx).id()
20568            })
20569        })
20570        .unwrap();
20571
20572    let buffer = project
20573        .update(cx, |project, cx| {
20574            project.open_buffer((worktree_id, "main.rs"), cx)
20575        })
20576        .await
20577        .unwrap();
20578
20579    let (editor, cx) = cx.add_window_view(|window, cx| {
20580        Editor::new(
20581            EditorMode::full(),
20582            MultiBuffer::build_from_buffer(buffer, cx),
20583            Some(project.clone()),
20584            window,
20585            cx,
20586        )
20587    });
20588
20589    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20590    let abs_path = project.read_with(cx, |project, cx| {
20591        project
20592            .absolute_path(&project_path, cx)
20593            .map(|path_buf| Arc::from(path_buf.to_owned()))
20594            .unwrap()
20595    });
20596
20597    // assert we can add breakpoint on the first line
20598    editor.update_in(cx, |editor, window, cx| {
20599        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20600        editor.move_to_end(&MoveToEnd, window, cx);
20601        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20602        editor.move_up(&MoveUp, window, cx);
20603        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20604    });
20605
20606    let breakpoints = editor.update(cx, |editor, cx| {
20607        editor
20608            .breakpoint_store()
20609            .as_ref()
20610            .unwrap()
20611            .read(cx)
20612            .all_source_breakpoints(cx)
20613            .clone()
20614    });
20615
20616    assert_eq!(1, breakpoints.len());
20617    assert_breakpoint(
20618        &breakpoints,
20619        &abs_path,
20620        vec![
20621            (0, Breakpoint::new_standard()),
20622            (2, Breakpoint::new_standard()),
20623            (3, Breakpoint::new_standard()),
20624        ],
20625    );
20626
20627    editor.update_in(cx, |editor, window, cx| {
20628        editor.move_to_beginning(&MoveToBeginning, window, cx);
20629        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20630        editor.move_to_end(&MoveToEnd, window, cx);
20631        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20632        // Disabling a breakpoint that doesn't exist should do nothing
20633        editor.move_up(&MoveUp, window, cx);
20634        editor.move_up(&MoveUp, window, cx);
20635        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20636    });
20637
20638    let breakpoints = editor.update(cx, |editor, cx| {
20639        editor
20640            .breakpoint_store()
20641            .as_ref()
20642            .unwrap()
20643            .read(cx)
20644            .all_source_breakpoints(cx)
20645            .clone()
20646    });
20647
20648    let disable_breakpoint = {
20649        let mut bp = Breakpoint::new_standard();
20650        bp.state = BreakpointState::Disabled;
20651        bp
20652    };
20653
20654    assert_eq!(1, breakpoints.len());
20655    assert_breakpoint(
20656        &breakpoints,
20657        &abs_path,
20658        vec![
20659            (0, disable_breakpoint.clone()),
20660            (2, Breakpoint::new_standard()),
20661            (3, disable_breakpoint.clone()),
20662        ],
20663    );
20664
20665    editor.update_in(cx, |editor, window, cx| {
20666        editor.move_to_beginning(&MoveToBeginning, window, cx);
20667        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20668        editor.move_to_end(&MoveToEnd, window, cx);
20669        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20670        editor.move_up(&MoveUp, window, cx);
20671        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20672    });
20673
20674    let breakpoints = editor.update(cx, |editor, cx| {
20675        editor
20676            .breakpoint_store()
20677            .as_ref()
20678            .unwrap()
20679            .read(cx)
20680            .all_source_breakpoints(cx)
20681            .clone()
20682    });
20683
20684    assert_eq!(1, breakpoints.len());
20685    assert_breakpoint(
20686        &breakpoints,
20687        &abs_path,
20688        vec![
20689            (0, Breakpoint::new_standard()),
20690            (2, disable_breakpoint),
20691            (3, Breakpoint::new_standard()),
20692        ],
20693    );
20694}
20695
20696#[gpui::test]
20697async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20698    init_test(cx, |_| {});
20699    let capabilities = lsp::ServerCapabilities {
20700        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20701            prepare_provider: Some(true),
20702            work_done_progress_options: Default::default(),
20703        })),
20704        ..Default::default()
20705    };
20706    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20707
20708    cx.set_state(indoc! {"
20709        struct Fˇoo {}
20710    "});
20711
20712    cx.update_editor(|editor, _, cx| {
20713        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20714        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20715        editor.highlight_background::<DocumentHighlightRead>(
20716            &[highlight_range],
20717            |theme| theme.colors().editor_document_highlight_read_background,
20718            cx,
20719        );
20720    });
20721
20722    let mut prepare_rename_handler = cx
20723        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20724            move |_, _, _| async move {
20725                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20726                    start: lsp::Position {
20727                        line: 0,
20728                        character: 7,
20729                    },
20730                    end: lsp::Position {
20731                        line: 0,
20732                        character: 10,
20733                    },
20734                })))
20735            },
20736        );
20737    let prepare_rename_task = cx
20738        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20739        .expect("Prepare rename was not started");
20740    prepare_rename_handler.next().await.unwrap();
20741    prepare_rename_task.await.expect("Prepare rename failed");
20742
20743    let mut rename_handler =
20744        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20745            let edit = lsp::TextEdit {
20746                range: lsp::Range {
20747                    start: lsp::Position {
20748                        line: 0,
20749                        character: 7,
20750                    },
20751                    end: lsp::Position {
20752                        line: 0,
20753                        character: 10,
20754                    },
20755                },
20756                new_text: "FooRenamed".to_string(),
20757            };
20758            Ok(Some(lsp::WorkspaceEdit::new(
20759                // Specify the same edit twice
20760                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20761            )))
20762        });
20763    let rename_task = cx
20764        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20765        .expect("Confirm rename was not started");
20766    rename_handler.next().await.unwrap();
20767    rename_task.await.expect("Confirm rename failed");
20768    cx.run_until_parked();
20769
20770    // Despite two edits, only one is actually applied as those are identical
20771    cx.assert_editor_state(indoc! {"
20772        struct FooRenamedˇ {}
20773    "});
20774}
20775
20776#[gpui::test]
20777async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20778    init_test(cx, |_| {});
20779    // These capabilities indicate that the server does not support prepare rename.
20780    let capabilities = lsp::ServerCapabilities {
20781        rename_provider: Some(lsp::OneOf::Left(true)),
20782        ..Default::default()
20783    };
20784    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20785
20786    cx.set_state(indoc! {"
20787        struct Fˇoo {}
20788    "});
20789
20790    cx.update_editor(|editor, _window, cx| {
20791        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20792        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20793        editor.highlight_background::<DocumentHighlightRead>(
20794            &[highlight_range],
20795            |theme| theme.colors().editor_document_highlight_read_background,
20796            cx,
20797        );
20798    });
20799
20800    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20801        .expect("Prepare rename was not started")
20802        .await
20803        .expect("Prepare rename failed");
20804
20805    let mut rename_handler =
20806        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20807            let edit = lsp::TextEdit {
20808                range: lsp::Range {
20809                    start: lsp::Position {
20810                        line: 0,
20811                        character: 7,
20812                    },
20813                    end: lsp::Position {
20814                        line: 0,
20815                        character: 10,
20816                    },
20817                },
20818                new_text: "FooRenamed".to_string(),
20819            };
20820            Ok(Some(lsp::WorkspaceEdit::new(
20821                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20822            )))
20823        });
20824    let rename_task = cx
20825        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20826        .expect("Confirm rename was not started");
20827    rename_handler.next().await.unwrap();
20828    rename_task.await.expect("Confirm rename failed");
20829    cx.run_until_parked();
20830
20831    // Correct range is renamed, as `surrounding_word` is used to find it.
20832    cx.assert_editor_state(indoc! {"
20833        struct FooRenamedˇ {}
20834    "});
20835}
20836
20837#[gpui::test]
20838async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20839    init_test(cx, |_| {});
20840    let mut cx = EditorTestContext::new(cx).await;
20841
20842    let language = Arc::new(
20843        Language::new(
20844            LanguageConfig::default(),
20845            Some(tree_sitter_html::LANGUAGE.into()),
20846        )
20847        .with_brackets_query(
20848            r#"
20849            ("<" @open "/>" @close)
20850            ("</" @open ">" @close)
20851            ("<" @open ">" @close)
20852            ("\"" @open "\"" @close)
20853            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20854        "#,
20855        )
20856        .unwrap(),
20857    );
20858    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20859
20860    cx.set_state(indoc! {"
20861        <span>ˇ</span>
20862    "});
20863    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20864    cx.assert_editor_state(indoc! {"
20865        <span>
20866        ˇ
20867        </span>
20868    "});
20869
20870    cx.set_state(indoc! {"
20871        <span><span></span>ˇ</span>
20872    "});
20873    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20874    cx.assert_editor_state(indoc! {"
20875        <span><span></span>
20876        ˇ</span>
20877    "});
20878
20879    cx.set_state(indoc! {"
20880        <span>ˇ
20881        </span>
20882    "});
20883    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20884    cx.assert_editor_state(indoc! {"
20885        <span>
20886        ˇ
20887        </span>
20888    "});
20889}
20890
20891#[gpui::test(iterations = 10)]
20892async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20893    init_test(cx, |_| {});
20894
20895    let fs = FakeFs::new(cx.executor());
20896    fs.insert_tree(
20897        path!("/dir"),
20898        json!({
20899            "a.ts": "a",
20900        }),
20901    )
20902    .await;
20903
20904    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20905    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20906    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20907
20908    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20909    language_registry.add(Arc::new(Language::new(
20910        LanguageConfig {
20911            name: "TypeScript".into(),
20912            matcher: LanguageMatcher {
20913                path_suffixes: vec!["ts".to_string()],
20914                ..Default::default()
20915            },
20916            ..Default::default()
20917        },
20918        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20919    )));
20920    let mut fake_language_servers = language_registry.register_fake_lsp(
20921        "TypeScript",
20922        FakeLspAdapter {
20923            capabilities: lsp::ServerCapabilities {
20924                code_lens_provider: Some(lsp::CodeLensOptions {
20925                    resolve_provider: Some(true),
20926                }),
20927                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20928                    commands: vec!["_the/command".to_string()],
20929                    ..lsp::ExecuteCommandOptions::default()
20930                }),
20931                ..lsp::ServerCapabilities::default()
20932            },
20933            ..FakeLspAdapter::default()
20934        },
20935    );
20936
20937    let (buffer, _handle) = project
20938        .update(cx, |p, cx| {
20939            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20940        })
20941        .await
20942        .unwrap();
20943    cx.executor().run_until_parked();
20944
20945    let fake_server = fake_language_servers.next().await.unwrap();
20946
20947    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20948    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20949    drop(buffer_snapshot);
20950    let actions = cx
20951        .update_window(*workspace, |_, window, cx| {
20952            project.code_actions(&buffer, anchor..anchor, window, cx)
20953        })
20954        .unwrap();
20955
20956    fake_server
20957        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20958            Ok(Some(vec![
20959                lsp::CodeLens {
20960                    range: lsp::Range::default(),
20961                    command: Some(lsp::Command {
20962                        title: "Code lens command".to_owned(),
20963                        command: "_the/command".to_owned(),
20964                        arguments: None,
20965                    }),
20966                    data: None,
20967                },
20968                lsp::CodeLens {
20969                    range: lsp::Range::default(),
20970                    command: Some(lsp::Command {
20971                        title: "Command not in capabilities".to_owned(),
20972                        command: "not in capabilities".to_owned(),
20973                        arguments: None,
20974                    }),
20975                    data: None,
20976                },
20977                lsp::CodeLens {
20978                    range: lsp::Range {
20979                        start: lsp::Position {
20980                            line: 1,
20981                            character: 1,
20982                        },
20983                        end: lsp::Position {
20984                            line: 1,
20985                            character: 1,
20986                        },
20987                    },
20988                    command: Some(lsp::Command {
20989                        title: "Command not in range".to_owned(),
20990                        command: "_the/command".to_owned(),
20991                        arguments: None,
20992                    }),
20993                    data: None,
20994                },
20995            ]))
20996        })
20997        .next()
20998        .await;
20999
21000    let actions = actions.await.unwrap();
21001    assert_eq!(
21002        actions.len(),
21003        1,
21004        "Should have only one valid action for the 0..0 range"
21005    );
21006    let action = actions[0].clone();
21007    let apply = project.update(cx, |project, cx| {
21008        project.apply_code_action(buffer.clone(), action, true, cx)
21009    });
21010
21011    // Resolving the code action does not populate its edits. In absence of
21012    // edits, we must execute the given command.
21013    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21014        |mut lens, _| async move {
21015            let lens_command = lens.command.as_mut().expect("should have a command");
21016            assert_eq!(lens_command.title, "Code lens command");
21017            lens_command.arguments = Some(vec![json!("the-argument")]);
21018            Ok(lens)
21019        },
21020    );
21021
21022    // While executing the command, the language server sends the editor
21023    // a `workspaceEdit` request.
21024    fake_server
21025        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21026            let fake = fake_server.clone();
21027            move |params, _| {
21028                assert_eq!(params.command, "_the/command");
21029                let fake = fake.clone();
21030                async move {
21031                    fake.server
21032                        .request::<lsp::request::ApplyWorkspaceEdit>(
21033                            lsp::ApplyWorkspaceEditParams {
21034                                label: None,
21035                                edit: lsp::WorkspaceEdit {
21036                                    changes: Some(
21037                                        [(
21038                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21039                                            vec![lsp::TextEdit {
21040                                                range: lsp::Range::new(
21041                                                    lsp::Position::new(0, 0),
21042                                                    lsp::Position::new(0, 0),
21043                                                ),
21044                                                new_text: "X".into(),
21045                                            }],
21046                                        )]
21047                                        .into_iter()
21048                                        .collect(),
21049                                    ),
21050                                    ..Default::default()
21051                                },
21052                            },
21053                        )
21054                        .await
21055                        .into_response()
21056                        .unwrap();
21057                    Ok(Some(json!(null)))
21058                }
21059            }
21060        })
21061        .next()
21062        .await;
21063
21064    // Applying the code lens command returns a project transaction containing the edits
21065    // sent by the language server in its `workspaceEdit` request.
21066    let transaction = apply.await.unwrap();
21067    assert!(transaction.0.contains_key(&buffer));
21068    buffer.update(cx, |buffer, cx| {
21069        assert_eq!(buffer.text(), "Xa");
21070        buffer.undo(cx);
21071        assert_eq!(buffer.text(), "a");
21072    });
21073}
21074
21075#[gpui::test]
21076async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21077    init_test(cx, |_| {});
21078
21079    let fs = FakeFs::new(cx.executor());
21080    let main_text = r#"fn main() {
21081println!("1");
21082println!("2");
21083println!("3");
21084println!("4");
21085println!("5");
21086}"#;
21087    let lib_text = "mod foo {}";
21088    fs.insert_tree(
21089        path!("/a"),
21090        json!({
21091            "lib.rs": lib_text,
21092            "main.rs": main_text,
21093        }),
21094    )
21095    .await;
21096
21097    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21098    let (workspace, cx) =
21099        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21100    let worktree_id = workspace.update(cx, |workspace, cx| {
21101        workspace.project().update(cx, |project, cx| {
21102            project.worktrees(cx).next().unwrap().read(cx).id()
21103        })
21104    });
21105
21106    let expected_ranges = vec![
21107        Point::new(0, 0)..Point::new(0, 0),
21108        Point::new(1, 0)..Point::new(1, 1),
21109        Point::new(2, 0)..Point::new(2, 2),
21110        Point::new(3, 0)..Point::new(3, 3),
21111    ];
21112
21113    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21114    let editor_1 = workspace
21115        .update_in(cx, |workspace, window, cx| {
21116            workspace.open_path(
21117                (worktree_id, "main.rs"),
21118                Some(pane_1.downgrade()),
21119                true,
21120                window,
21121                cx,
21122            )
21123        })
21124        .unwrap()
21125        .await
21126        .downcast::<Editor>()
21127        .unwrap();
21128    pane_1.update(cx, |pane, cx| {
21129        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21130        open_editor.update(cx, |editor, cx| {
21131            assert_eq!(
21132                editor.display_text(cx),
21133                main_text,
21134                "Original main.rs text on initial open",
21135            );
21136            assert_eq!(
21137                editor
21138                    .selections
21139                    .all::<Point>(cx)
21140                    .into_iter()
21141                    .map(|s| s.range())
21142                    .collect::<Vec<_>>(),
21143                vec![Point::zero()..Point::zero()],
21144                "Default selections on initial open",
21145            );
21146        })
21147    });
21148    editor_1.update_in(cx, |editor, window, cx| {
21149        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21150            s.select_ranges(expected_ranges.clone());
21151        });
21152    });
21153
21154    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21155        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21156    });
21157    let editor_2 = workspace
21158        .update_in(cx, |workspace, window, cx| {
21159            workspace.open_path(
21160                (worktree_id, "main.rs"),
21161                Some(pane_2.downgrade()),
21162                true,
21163                window,
21164                cx,
21165            )
21166        })
21167        .unwrap()
21168        .await
21169        .downcast::<Editor>()
21170        .unwrap();
21171    pane_2.update(cx, |pane, cx| {
21172        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21173        open_editor.update(cx, |editor, cx| {
21174            assert_eq!(
21175                editor.display_text(cx),
21176                main_text,
21177                "Original main.rs text on initial open in another panel",
21178            );
21179            assert_eq!(
21180                editor
21181                    .selections
21182                    .all::<Point>(cx)
21183                    .into_iter()
21184                    .map(|s| s.range())
21185                    .collect::<Vec<_>>(),
21186                vec![Point::zero()..Point::zero()],
21187                "Default selections on initial open in another panel",
21188            );
21189        })
21190    });
21191
21192    editor_2.update_in(cx, |editor, window, cx| {
21193        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21194    });
21195
21196    let _other_editor_1 = workspace
21197        .update_in(cx, |workspace, window, cx| {
21198            workspace.open_path(
21199                (worktree_id, "lib.rs"),
21200                Some(pane_1.downgrade()),
21201                true,
21202                window,
21203                cx,
21204            )
21205        })
21206        .unwrap()
21207        .await
21208        .downcast::<Editor>()
21209        .unwrap();
21210    pane_1
21211        .update_in(cx, |pane, window, cx| {
21212            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21213        })
21214        .await
21215        .unwrap();
21216    drop(editor_1);
21217    pane_1.update(cx, |pane, cx| {
21218        pane.active_item()
21219            .unwrap()
21220            .downcast::<Editor>()
21221            .unwrap()
21222            .update(cx, |editor, cx| {
21223                assert_eq!(
21224                    editor.display_text(cx),
21225                    lib_text,
21226                    "Other file should be open and active",
21227                );
21228            });
21229        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21230    });
21231
21232    let _other_editor_2 = workspace
21233        .update_in(cx, |workspace, window, cx| {
21234            workspace.open_path(
21235                (worktree_id, "lib.rs"),
21236                Some(pane_2.downgrade()),
21237                true,
21238                window,
21239                cx,
21240            )
21241        })
21242        .unwrap()
21243        .await
21244        .downcast::<Editor>()
21245        .unwrap();
21246    pane_2
21247        .update_in(cx, |pane, window, cx| {
21248            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21249        })
21250        .await
21251        .unwrap();
21252    drop(editor_2);
21253    pane_2.update(cx, |pane, cx| {
21254        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21255        open_editor.update(cx, |editor, cx| {
21256            assert_eq!(
21257                editor.display_text(cx),
21258                lib_text,
21259                "Other file should be open and active in another panel too",
21260            );
21261        });
21262        assert_eq!(
21263            pane.items().count(),
21264            1,
21265            "No other editors should be open in another pane",
21266        );
21267    });
21268
21269    let _editor_1_reopened = workspace
21270        .update_in(cx, |workspace, window, cx| {
21271            workspace.open_path(
21272                (worktree_id, "main.rs"),
21273                Some(pane_1.downgrade()),
21274                true,
21275                window,
21276                cx,
21277            )
21278        })
21279        .unwrap()
21280        .await
21281        .downcast::<Editor>()
21282        .unwrap();
21283    let _editor_2_reopened = workspace
21284        .update_in(cx, |workspace, window, cx| {
21285            workspace.open_path(
21286                (worktree_id, "main.rs"),
21287                Some(pane_2.downgrade()),
21288                true,
21289                window,
21290                cx,
21291            )
21292        })
21293        .unwrap()
21294        .await
21295        .downcast::<Editor>()
21296        .unwrap();
21297    pane_1.update(cx, |pane, cx| {
21298        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21299        open_editor.update(cx, |editor, cx| {
21300            assert_eq!(
21301                editor.display_text(cx),
21302                main_text,
21303                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21304            );
21305            assert_eq!(
21306                editor
21307                    .selections
21308                    .all::<Point>(cx)
21309                    .into_iter()
21310                    .map(|s| s.range())
21311                    .collect::<Vec<_>>(),
21312                expected_ranges,
21313                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21314            );
21315        })
21316    });
21317    pane_2.update(cx, |pane, cx| {
21318        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21319        open_editor.update(cx, |editor, cx| {
21320            assert_eq!(
21321                editor.display_text(cx),
21322                r#"fn main() {
21323⋯rintln!("1");
21324⋯intln!("2");
21325⋯ntln!("3");
21326println!("4");
21327println!("5");
21328}"#,
21329                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21330            );
21331            assert_eq!(
21332                editor
21333                    .selections
21334                    .all::<Point>(cx)
21335                    .into_iter()
21336                    .map(|s| s.range())
21337                    .collect::<Vec<_>>(),
21338                vec![Point::zero()..Point::zero()],
21339                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21340            );
21341        })
21342    });
21343}
21344
21345#[gpui::test]
21346async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21347    init_test(cx, |_| {});
21348
21349    let fs = FakeFs::new(cx.executor());
21350    let main_text = r#"fn main() {
21351println!("1");
21352println!("2");
21353println!("3");
21354println!("4");
21355println!("5");
21356}"#;
21357    let lib_text = "mod foo {}";
21358    fs.insert_tree(
21359        path!("/a"),
21360        json!({
21361            "lib.rs": lib_text,
21362            "main.rs": main_text,
21363        }),
21364    )
21365    .await;
21366
21367    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21368    let (workspace, cx) =
21369        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21370    let worktree_id = workspace.update(cx, |workspace, cx| {
21371        workspace.project().update(cx, |project, cx| {
21372            project.worktrees(cx).next().unwrap().read(cx).id()
21373        })
21374    });
21375
21376    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21377    let editor = workspace
21378        .update_in(cx, |workspace, window, cx| {
21379            workspace.open_path(
21380                (worktree_id, "main.rs"),
21381                Some(pane.downgrade()),
21382                true,
21383                window,
21384                cx,
21385            )
21386        })
21387        .unwrap()
21388        .await
21389        .downcast::<Editor>()
21390        .unwrap();
21391    pane.update(cx, |pane, cx| {
21392        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21393        open_editor.update(cx, |editor, cx| {
21394            assert_eq!(
21395                editor.display_text(cx),
21396                main_text,
21397                "Original main.rs text on initial open",
21398            );
21399        })
21400    });
21401    editor.update_in(cx, |editor, window, cx| {
21402        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21403    });
21404
21405    cx.update_global(|store: &mut SettingsStore, cx| {
21406        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21407            s.restore_on_file_reopen = Some(false);
21408        });
21409    });
21410    editor.update_in(cx, |editor, window, cx| {
21411        editor.fold_ranges(
21412            vec![
21413                Point::new(1, 0)..Point::new(1, 1),
21414                Point::new(2, 0)..Point::new(2, 2),
21415                Point::new(3, 0)..Point::new(3, 3),
21416            ],
21417            false,
21418            window,
21419            cx,
21420        );
21421    });
21422    pane.update_in(cx, |pane, window, cx| {
21423        pane.close_all_items(&CloseAllItems::default(), window, cx)
21424    })
21425    .await
21426    .unwrap();
21427    pane.update(cx, |pane, _| {
21428        assert!(pane.active_item().is_none());
21429    });
21430    cx.update_global(|store: &mut SettingsStore, cx| {
21431        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21432            s.restore_on_file_reopen = Some(true);
21433        });
21434    });
21435
21436    let _editor_reopened = workspace
21437        .update_in(cx, |workspace, window, cx| {
21438            workspace.open_path(
21439                (worktree_id, "main.rs"),
21440                Some(pane.downgrade()),
21441                true,
21442                window,
21443                cx,
21444            )
21445        })
21446        .unwrap()
21447        .await
21448        .downcast::<Editor>()
21449        .unwrap();
21450    pane.update(cx, |pane, cx| {
21451        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21452        open_editor.update(cx, |editor, cx| {
21453            assert_eq!(
21454                editor.display_text(cx),
21455                main_text,
21456                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21457            );
21458        })
21459    });
21460}
21461
21462#[gpui::test]
21463async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21464    struct EmptyModalView {
21465        focus_handle: gpui::FocusHandle,
21466    }
21467    impl EventEmitter<DismissEvent> for EmptyModalView {}
21468    impl Render for EmptyModalView {
21469        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21470            div()
21471        }
21472    }
21473    impl Focusable for EmptyModalView {
21474        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21475            self.focus_handle.clone()
21476        }
21477    }
21478    impl workspace::ModalView for EmptyModalView {}
21479    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21480        EmptyModalView {
21481            focus_handle: cx.focus_handle(),
21482        }
21483    }
21484
21485    init_test(cx, |_| {});
21486
21487    let fs = FakeFs::new(cx.executor());
21488    let project = Project::test(fs, [], cx).await;
21489    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21490    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21491    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21492    let editor = cx.new_window_entity(|window, cx| {
21493        Editor::new(
21494            EditorMode::full(),
21495            buffer,
21496            Some(project.clone()),
21497            window,
21498            cx,
21499        )
21500    });
21501    workspace
21502        .update(cx, |workspace, window, cx| {
21503            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21504        })
21505        .unwrap();
21506    editor.update_in(cx, |editor, window, cx| {
21507        editor.open_context_menu(&OpenContextMenu, window, cx);
21508        assert!(editor.mouse_context_menu.is_some());
21509    });
21510    workspace
21511        .update(cx, |workspace, window, cx| {
21512            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21513        })
21514        .unwrap();
21515    cx.read(|cx| {
21516        assert!(editor.read(cx).mouse_context_menu.is_none());
21517    });
21518}
21519
21520#[gpui::test]
21521async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21522    init_test(cx, |_| {});
21523
21524    let fs = FakeFs::new(cx.executor());
21525    fs.insert_file(path!("/file.html"), Default::default())
21526        .await;
21527
21528    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21529
21530    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21531    let html_language = Arc::new(Language::new(
21532        LanguageConfig {
21533            name: "HTML".into(),
21534            matcher: LanguageMatcher {
21535                path_suffixes: vec!["html".to_string()],
21536                ..LanguageMatcher::default()
21537            },
21538            brackets: BracketPairConfig {
21539                pairs: vec![BracketPair {
21540                    start: "<".into(),
21541                    end: ">".into(),
21542                    close: true,
21543                    ..Default::default()
21544                }],
21545                ..Default::default()
21546            },
21547            ..Default::default()
21548        },
21549        Some(tree_sitter_html::LANGUAGE.into()),
21550    ));
21551    language_registry.add(html_language);
21552    let mut fake_servers = language_registry.register_fake_lsp(
21553        "HTML",
21554        FakeLspAdapter {
21555            capabilities: lsp::ServerCapabilities {
21556                completion_provider: Some(lsp::CompletionOptions {
21557                    resolve_provider: Some(true),
21558                    ..Default::default()
21559                }),
21560                ..Default::default()
21561            },
21562            ..Default::default()
21563        },
21564    );
21565
21566    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21567    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21568
21569    let worktree_id = workspace
21570        .update(cx, |workspace, _window, cx| {
21571            workspace.project().update(cx, |project, cx| {
21572                project.worktrees(cx).next().unwrap().read(cx).id()
21573            })
21574        })
21575        .unwrap();
21576    project
21577        .update(cx, |project, cx| {
21578            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21579        })
21580        .await
21581        .unwrap();
21582    let editor = workspace
21583        .update(cx, |workspace, window, cx| {
21584            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21585        })
21586        .unwrap()
21587        .await
21588        .unwrap()
21589        .downcast::<Editor>()
21590        .unwrap();
21591
21592    let fake_server = fake_servers.next().await.unwrap();
21593    editor.update_in(cx, |editor, window, cx| {
21594        editor.set_text("<ad></ad>", window, cx);
21595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21596            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21597        });
21598        let Some((buffer, _)) = editor
21599            .buffer
21600            .read(cx)
21601            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21602        else {
21603            panic!("Failed to get buffer for selection position");
21604        };
21605        let buffer = buffer.read(cx);
21606        let buffer_id = buffer.remote_id();
21607        let opening_range =
21608            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21609        let closing_range =
21610            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21611        let mut linked_ranges = HashMap::default();
21612        linked_ranges.insert(
21613            buffer_id,
21614            vec![(opening_range.clone(), vec![closing_range.clone()])],
21615        );
21616        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21617    });
21618    let mut completion_handle =
21619        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21620            Ok(Some(lsp::CompletionResponse::Array(vec![
21621                lsp::CompletionItem {
21622                    label: "head".to_string(),
21623                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21624                        lsp::InsertReplaceEdit {
21625                            new_text: "head".to_string(),
21626                            insert: lsp::Range::new(
21627                                lsp::Position::new(0, 1),
21628                                lsp::Position::new(0, 3),
21629                            ),
21630                            replace: lsp::Range::new(
21631                                lsp::Position::new(0, 1),
21632                                lsp::Position::new(0, 3),
21633                            ),
21634                        },
21635                    )),
21636                    ..Default::default()
21637                },
21638            ])))
21639        });
21640    editor.update_in(cx, |editor, window, cx| {
21641        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21642    });
21643    cx.run_until_parked();
21644    completion_handle.next().await.unwrap();
21645    editor.update(cx, |editor, _| {
21646        assert!(
21647            editor.context_menu_visible(),
21648            "Completion menu should be visible"
21649        );
21650    });
21651    editor.update_in(cx, |editor, window, cx| {
21652        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21653    });
21654    cx.executor().run_until_parked();
21655    editor.update(cx, |editor, cx| {
21656        assert_eq!(editor.text(cx), "<head></head>");
21657    });
21658}
21659
21660#[gpui::test]
21661async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21662    init_test(cx, |_| {});
21663
21664    let fs = FakeFs::new(cx.executor());
21665    fs.insert_tree(
21666        path!("/root"),
21667        json!({
21668            "a": {
21669                "main.rs": "fn main() {}",
21670            },
21671            "foo": {
21672                "bar": {
21673                    "external_file.rs": "pub mod external {}",
21674                }
21675            }
21676        }),
21677    )
21678    .await;
21679
21680    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21681    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21682    language_registry.add(rust_lang());
21683    let _fake_servers = language_registry.register_fake_lsp(
21684        "Rust",
21685        FakeLspAdapter {
21686            ..FakeLspAdapter::default()
21687        },
21688    );
21689    let (workspace, cx) =
21690        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21691    let worktree_id = workspace.update(cx, |workspace, cx| {
21692        workspace.project().update(cx, |project, cx| {
21693            project.worktrees(cx).next().unwrap().read(cx).id()
21694        })
21695    });
21696
21697    let assert_language_servers_count =
21698        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21699            project.update(cx, |project, cx| {
21700                let current = project
21701                    .lsp_store()
21702                    .read(cx)
21703                    .as_local()
21704                    .unwrap()
21705                    .language_servers
21706                    .len();
21707                assert_eq!(expected, current, "{context}");
21708            });
21709        };
21710
21711    assert_language_servers_count(
21712        0,
21713        "No servers should be running before any file is open",
21714        cx,
21715    );
21716    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21717    let main_editor = workspace
21718        .update_in(cx, |workspace, window, cx| {
21719            workspace.open_path(
21720                (worktree_id, "main.rs"),
21721                Some(pane.downgrade()),
21722                true,
21723                window,
21724                cx,
21725            )
21726        })
21727        .unwrap()
21728        .await
21729        .downcast::<Editor>()
21730        .unwrap();
21731    pane.update(cx, |pane, cx| {
21732        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21733        open_editor.update(cx, |editor, cx| {
21734            assert_eq!(
21735                editor.display_text(cx),
21736                "fn main() {}",
21737                "Original main.rs text on initial open",
21738            );
21739        });
21740        assert_eq!(open_editor, main_editor);
21741    });
21742    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21743
21744    let external_editor = workspace
21745        .update_in(cx, |workspace, window, cx| {
21746            workspace.open_abs_path(
21747                PathBuf::from("/root/foo/bar/external_file.rs"),
21748                OpenOptions::default(),
21749                window,
21750                cx,
21751            )
21752        })
21753        .await
21754        .expect("opening external file")
21755        .downcast::<Editor>()
21756        .expect("downcasted external file's open element to editor");
21757    pane.update(cx, |pane, cx| {
21758        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21759        open_editor.update(cx, |editor, cx| {
21760            assert_eq!(
21761                editor.display_text(cx),
21762                "pub mod external {}",
21763                "External file is open now",
21764            );
21765        });
21766        assert_eq!(open_editor, external_editor);
21767    });
21768    assert_language_servers_count(
21769        1,
21770        "Second, external, *.rs file should join the existing server",
21771        cx,
21772    );
21773
21774    pane.update_in(cx, |pane, window, cx| {
21775        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21776    })
21777    .await
21778    .unwrap();
21779    pane.update_in(cx, |pane, window, cx| {
21780        pane.navigate_backward(window, cx);
21781    });
21782    cx.run_until_parked();
21783    pane.update(cx, |pane, cx| {
21784        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21785        open_editor.update(cx, |editor, cx| {
21786            assert_eq!(
21787                editor.display_text(cx),
21788                "pub mod external {}",
21789                "External file is open now",
21790            );
21791        });
21792    });
21793    assert_language_servers_count(
21794        1,
21795        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21796        cx,
21797    );
21798
21799    cx.update(|_, cx| {
21800        workspace::reload(&workspace::Reload::default(), cx);
21801    });
21802    assert_language_servers_count(
21803        1,
21804        "After reloading the worktree with local and external files opened, only one project should be started",
21805        cx,
21806    );
21807}
21808
21809#[gpui::test]
21810async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21811    init_test(cx, |_| {});
21812
21813    let mut cx = EditorTestContext::new(cx).await;
21814    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21816
21817    // test cursor move to start of each line on tab
21818    // for `if`, `elif`, `else`, `while`, `with` and `for`
21819    cx.set_state(indoc! {"
21820        def main():
21821        ˇ    for item in items:
21822        ˇ        while item.active:
21823        ˇ            if item.value > 10:
21824        ˇ                continue
21825        ˇ            elif item.value < 0:
21826        ˇ                break
21827        ˇ            else:
21828        ˇ                with item.context() as ctx:
21829        ˇ                    yield count
21830        ˇ        else:
21831        ˇ            log('while else')
21832        ˇ    else:
21833        ˇ        log('for else')
21834    "});
21835    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21836    cx.assert_editor_state(indoc! {"
21837        def main():
21838            ˇfor item in items:
21839                ˇwhile item.active:
21840                    ˇif item.value > 10:
21841                        ˇcontinue
21842                    ˇelif item.value < 0:
21843                        ˇbreak
21844                    ˇelse:
21845                        ˇwith item.context() as ctx:
21846                            ˇyield count
21847                ˇelse:
21848                    ˇlog('while else')
21849            ˇelse:
21850                ˇlog('for else')
21851    "});
21852    // test relative indent is preserved when tab
21853    // for `if`, `elif`, `else`, `while`, `with` and `for`
21854    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21855    cx.assert_editor_state(indoc! {"
21856        def main():
21857                ˇfor item in items:
21858                    ˇwhile item.active:
21859                        ˇif item.value > 10:
21860                            ˇcontinue
21861                        ˇelif item.value < 0:
21862                            ˇbreak
21863                        ˇelse:
21864                            ˇwith item.context() as ctx:
21865                                ˇyield count
21866                    ˇelse:
21867                        ˇlog('while else')
21868                ˇelse:
21869                    ˇlog('for else')
21870    "});
21871
21872    // test cursor move to start of each line on tab
21873    // for `try`, `except`, `else`, `finally`, `match` and `def`
21874    cx.set_state(indoc! {"
21875        def main():
21876        ˇ    try:
21877        ˇ        fetch()
21878        ˇ    except ValueError:
21879        ˇ        handle_error()
21880        ˇ    else:
21881        ˇ        match value:
21882        ˇ            case _:
21883        ˇ    finally:
21884        ˇ        def status():
21885        ˇ            return 0
21886    "});
21887    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21888    cx.assert_editor_state(indoc! {"
21889        def main():
21890            ˇtry:
21891                ˇfetch()
21892            ˇexcept ValueError:
21893                ˇhandle_error()
21894            ˇelse:
21895                ˇmatch value:
21896                    ˇcase _:
21897            ˇfinally:
21898                ˇdef status():
21899                    ˇreturn 0
21900    "});
21901    // test relative indent is preserved when tab
21902    // for `try`, `except`, `else`, `finally`, `match` and `def`
21903    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21904    cx.assert_editor_state(indoc! {"
21905        def main():
21906                ˇtry:
21907                    ˇfetch()
21908                ˇexcept ValueError:
21909                    ˇhandle_error()
21910                ˇelse:
21911                    ˇmatch value:
21912                        ˇcase _:
21913                ˇfinally:
21914                    ˇdef status():
21915                        ˇreturn 0
21916    "});
21917}
21918
21919#[gpui::test]
21920async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21921    init_test(cx, |_| {});
21922
21923    let mut cx = EditorTestContext::new(cx).await;
21924    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21925    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21926
21927    // test `else` auto outdents when typed inside `if` block
21928    cx.set_state(indoc! {"
21929        def main():
21930            if i == 2:
21931                return
21932                ˇ
21933    "});
21934    cx.update_editor(|editor, window, cx| {
21935        editor.handle_input("else:", window, cx);
21936    });
21937    cx.assert_editor_state(indoc! {"
21938        def main():
21939            if i == 2:
21940                return
21941            else:ˇ
21942    "});
21943
21944    // test `except` auto outdents when typed inside `try` block
21945    cx.set_state(indoc! {"
21946        def main():
21947            try:
21948                i = 2
21949                ˇ
21950    "});
21951    cx.update_editor(|editor, window, cx| {
21952        editor.handle_input("except:", window, cx);
21953    });
21954    cx.assert_editor_state(indoc! {"
21955        def main():
21956            try:
21957                i = 2
21958            except:ˇ
21959    "});
21960
21961    // test `else` auto outdents when typed inside `except` block
21962    cx.set_state(indoc! {"
21963        def main():
21964            try:
21965                i = 2
21966            except:
21967                j = 2
21968                ˇ
21969    "});
21970    cx.update_editor(|editor, window, cx| {
21971        editor.handle_input("else:", window, cx);
21972    });
21973    cx.assert_editor_state(indoc! {"
21974        def main():
21975            try:
21976                i = 2
21977            except:
21978                j = 2
21979            else:ˇ
21980    "});
21981
21982    // test `finally` auto outdents when typed inside `else` block
21983    cx.set_state(indoc! {"
21984        def main():
21985            try:
21986                i = 2
21987            except:
21988                j = 2
21989            else:
21990                k = 2
21991                ˇ
21992    "});
21993    cx.update_editor(|editor, window, cx| {
21994        editor.handle_input("finally:", window, cx);
21995    });
21996    cx.assert_editor_state(indoc! {"
21997        def main():
21998            try:
21999                i = 2
22000            except:
22001                j = 2
22002            else:
22003                k = 2
22004            finally:ˇ
22005    "});
22006
22007    // test `else` does not outdents when typed inside `except` block right after for block
22008    cx.set_state(indoc! {"
22009        def main():
22010            try:
22011                i = 2
22012            except:
22013                for i in range(n):
22014                    pass
22015                ˇ
22016    "});
22017    cx.update_editor(|editor, window, cx| {
22018        editor.handle_input("else:", window, cx);
22019    });
22020    cx.assert_editor_state(indoc! {"
22021        def main():
22022            try:
22023                i = 2
22024            except:
22025                for i in range(n):
22026                    pass
22027                else:ˇ
22028    "});
22029
22030    // test `finally` auto outdents when typed inside `else` block right after for block
22031    cx.set_state(indoc! {"
22032        def main():
22033            try:
22034                i = 2
22035            except:
22036                j = 2
22037            else:
22038                for i in range(n):
22039                    pass
22040                ˇ
22041    "});
22042    cx.update_editor(|editor, window, cx| {
22043        editor.handle_input("finally:", window, cx);
22044    });
22045    cx.assert_editor_state(indoc! {"
22046        def main():
22047            try:
22048                i = 2
22049            except:
22050                j = 2
22051            else:
22052                for i in range(n):
22053                    pass
22054            finally:ˇ
22055    "});
22056
22057    // test `except` outdents to inner "try" block
22058    cx.set_state(indoc! {"
22059        def main():
22060            try:
22061                i = 2
22062                if i == 2:
22063                    try:
22064                        i = 3
22065                        ˇ
22066    "});
22067    cx.update_editor(|editor, window, cx| {
22068        editor.handle_input("except:", window, cx);
22069    });
22070    cx.assert_editor_state(indoc! {"
22071        def main():
22072            try:
22073                i = 2
22074                if i == 2:
22075                    try:
22076                        i = 3
22077                    except:ˇ
22078    "});
22079
22080    // test `except` outdents to outer "try" block
22081    cx.set_state(indoc! {"
22082        def main():
22083            try:
22084                i = 2
22085                if i == 2:
22086                    try:
22087                        i = 3
22088                ˇ
22089    "});
22090    cx.update_editor(|editor, window, cx| {
22091        editor.handle_input("except:", window, cx);
22092    });
22093    cx.assert_editor_state(indoc! {"
22094        def main():
22095            try:
22096                i = 2
22097                if i == 2:
22098                    try:
22099                        i = 3
22100            except:ˇ
22101    "});
22102
22103    // test `else` stays at correct indent when typed after `for` block
22104    cx.set_state(indoc! {"
22105        def main():
22106            for i in range(10):
22107                if i == 3:
22108                    break
22109            ˇ
22110    "});
22111    cx.update_editor(|editor, window, cx| {
22112        editor.handle_input("else:", window, cx);
22113    });
22114    cx.assert_editor_state(indoc! {"
22115        def main():
22116            for i in range(10):
22117                if i == 3:
22118                    break
22119            else:ˇ
22120    "});
22121
22122    // test does not outdent on typing after line with square brackets
22123    cx.set_state(indoc! {"
22124        def f() -> list[str]:
22125            ˇ
22126    "});
22127    cx.update_editor(|editor, window, cx| {
22128        editor.handle_input("a", window, cx);
22129    });
22130    cx.assert_editor_state(indoc! {"
22131        def f() -> list[str]:
2213222133    "});
22134}
22135
22136#[gpui::test]
22137async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22138    init_test(cx, |_| {});
22139    update_test_language_settings(cx, |settings| {
22140        settings.defaults.extend_comment_on_newline = Some(false);
22141    });
22142    let mut cx = EditorTestContext::new(cx).await;
22143    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22144    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22145
22146    // test correct indent after newline on comment
22147    cx.set_state(indoc! {"
22148        # COMMENT:ˇ
22149    "});
22150    cx.update_editor(|editor, window, cx| {
22151        editor.newline(&Newline, window, cx);
22152    });
22153    cx.assert_editor_state(indoc! {"
22154        # COMMENT:
22155        ˇ
22156    "});
22157
22158    // test correct indent after newline in brackets
22159    cx.set_state(indoc! {"
22160        {ˇ}
22161    "});
22162    cx.update_editor(|editor, window, cx| {
22163        editor.newline(&Newline, window, cx);
22164    });
22165    cx.run_until_parked();
22166    cx.assert_editor_state(indoc! {"
22167        {
22168            ˇ
22169        }
22170    "});
22171
22172    cx.set_state(indoc! {"
22173        (ˇ)
22174    "});
22175    cx.update_editor(|editor, window, cx| {
22176        editor.newline(&Newline, window, cx);
22177    });
22178    cx.run_until_parked();
22179    cx.assert_editor_state(indoc! {"
22180        (
22181            ˇ
22182        )
22183    "});
22184
22185    // do not indent after empty lists or dictionaries
22186    cx.set_state(indoc! {"
22187        a = []ˇ
22188    "});
22189    cx.update_editor(|editor, window, cx| {
22190        editor.newline(&Newline, window, cx);
22191    });
22192    cx.run_until_parked();
22193    cx.assert_editor_state(indoc! {"
22194        a = []
22195        ˇ
22196    "});
22197}
22198
22199fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22200    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22201    point..point
22202}
22203
22204#[track_caller]
22205fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22206    let (text, ranges) = marked_text_ranges(marked_text, true);
22207    assert_eq!(editor.text(cx), text);
22208    assert_eq!(
22209        editor.selections.ranges(cx),
22210        ranges,
22211        "Assert selections are {}",
22212        marked_text
22213    );
22214}
22215
22216pub fn handle_signature_help_request(
22217    cx: &mut EditorLspTestContext,
22218    mocked_response: lsp::SignatureHelp,
22219) -> impl Future<Output = ()> + use<> {
22220    let mut request =
22221        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22222            let mocked_response = mocked_response.clone();
22223            async move { Ok(Some(mocked_response)) }
22224        });
22225
22226    async move {
22227        request.next().await;
22228    }
22229}
22230
22231#[track_caller]
22232pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22233    cx.update_editor(|editor, _, _| {
22234        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22235            let entries = menu.entries.borrow();
22236            let entries = entries
22237                .iter()
22238                .map(|entry| entry.string.as_str())
22239                .collect::<Vec<_>>();
22240            assert_eq!(entries, expected);
22241        } else {
22242            panic!("Expected completions menu");
22243        }
22244    });
22245}
22246
22247/// Handle completion request passing a marked string specifying where the completion
22248/// should be triggered from using '|' character, what range should be replaced, and what completions
22249/// should be returned using '<' and '>' to delimit the range.
22250///
22251/// Also see `handle_completion_request_with_insert_and_replace`.
22252#[track_caller]
22253pub fn handle_completion_request(
22254    marked_string: &str,
22255    completions: Vec<&'static str>,
22256    is_incomplete: bool,
22257    counter: Arc<AtomicUsize>,
22258    cx: &mut EditorLspTestContext,
22259) -> impl Future<Output = ()> {
22260    let complete_from_marker: TextRangeMarker = '|'.into();
22261    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22262    let (_, mut marked_ranges) = marked_text_ranges_by(
22263        marked_string,
22264        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22265    );
22266
22267    let complete_from_position =
22268        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22269    let replace_range =
22270        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22271
22272    let mut request =
22273        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22274            let completions = completions.clone();
22275            counter.fetch_add(1, atomic::Ordering::Release);
22276            async move {
22277                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22278                assert_eq!(
22279                    params.text_document_position.position,
22280                    complete_from_position
22281                );
22282                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22283                    is_incomplete: is_incomplete,
22284                    item_defaults: None,
22285                    items: completions
22286                        .iter()
22287                        .map(|completion_text| lsp::CompletionItem {
22288                            label: completion_text.to_string(),
22289                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22290                                range: replace_range,
22291                                new_text: completion_text.to_string(),
22292                            })),
22293                            ..Default::default()
22294                        })
22295                        .collect(),
22296                })))
22297            }
22298        });
22299
22300    async move {
22301        request.next().await;
22302    }
22303}
22304
22305/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22306/// given instead, which also contains an `insert` range.
22307///
22308/// This function uses markers to define ranges:
22309/// - `|` marks the cursor position
22310/// - `<>` marks the replace range
22311/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22312pub fn handle_completion_request_with_insert_and_replace(
22313    cx: &mut EditorLspTestContext,
22314    marked_string: &str,
22315    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22316    counter: Arc<AtomicUsize>,
22317) -> impl Future<Output = ()> {
22318    let complete_from_marker: TextRangeMarker = '|'.into();
22319    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22320    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22321
22322    let (_, mut marked_ranges) = marked_text_ranges_by(
22323        marked_string,
22324        vec![
22325            complete_from_marker.clone(),
22326            replace_range_marker.clone(),
22327            insert_range_marker.clone(),
22328        ],
22329    );
22330
22331    let complete_from_position =
22332        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22333    let replace_range =
22334        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22335
22336    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22337        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22338        _ => lsp::Range {
22339            start: replace_range.start,
22340            end: complete_from_position,
22341        },
22342    };
22343
22344    let mut request =
22345        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22346            let completions = completions.clone();
22347            counter.fetch_add(1, atomic::Ordering::Release);
22348            async move {
22349                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22350                assert_eq!(
22351                    params.text_document_position.position, complete_from_position,
22352                    "marker `|` position doesn't match",
22353                );
22354                Ok(Some(lsp::CompletionResponse::Array(
22355                    completions
22356                        .iter()
22357                        .map(|(label, new_text)| lsp::CompletionItem {
22358                            label: label.to_string(),
22359                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22360                                lsp::InsertReplaceEdit {
22361                                    insert: insert_range,
22362                                    replace: replace_range,
22363                                    new_text: new_text.to_string(),
22364                                },
22365                            )),
22366                            ..Default::default()
22367                        })
22368                        .collect(),
22369                )))
22370            }
22371        });
22372
22373    async move {
22374        request.next().await;
22375    }
22376}
22377
22378fn handle_resolve_completion_request(
22379    cx: &mut EditorLspTestContext,
22380    edits: Option<Vec<(&'static str, &'static str)>>,
22381) -> impl Future<Output = ()> {
22382    let edits = edits.map(|edits| {
22383        edits
22384            .iter()
22385            .map(|(marked_string, new_text)| {
22386                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22387                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22388                lsp::TextEdit::new(replace_range, new_text.to_string())
22389            })
22390            .collect::<Vec<_>>()
22391    });
22392
22393    let mut request =
22394        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22395            let edits = edits.clone();
22396            async move {
22397                Ok(lsp::CompletionItem {
22398                    additional_text_edits: edits,
22399                    ..Default::default()
22400                })
22401            }
22402        });
22403
22404    async move {
22405        request.next().await;
22406    }
22407}
22408
22409pub(crate) fn update_test_language_settings(
22410    cx: &mut TestAppContext,
22411    f: impl Fn(&mut AllLanguageSettingsContent),
22412) {
22413    cx.update(|cx| {
22414        SettingsStore::update_global(cx, |store, cx| {
22415            store.update_user_settings::<AllLanguageSettings>(cx, f);
22416        });
22417    });
22418}
22419
22420pub(crate) fn update_test_project_settings(
22421    cx: &mut TestAppContext,
22422    f: impl Fn(&mut ProjectSettings),
22423) {
22424    cx.update(|cx| {
22425        SettingsStore::update_global(cx, |store, cx| {
22426            store.update_user_settings::<ProjectSettings>(cx, f);
22427        });
22428    });
22429}
22430
22431pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22432    cx.update(|cx| {
22433        assets::Assets.load_test_fonts(cx);
22434        let store = SettingsStore::test(cx);
22435        cx.set_global(store);
22436        theme::init(theme::LoadThemes::JustBase, cx);
22437        release_channel::init(SemanticVersion::default(), cx);
22438        client::init_settings(cx);
22439        language::init(cx);
22440        Project::init_settings(cx);
22441        workspace::init_settings(cx);
22442        crate::init(cx);
22443    });
22444
22445    update_test_language_settings(cx, f);
22446}
22447
22448#[track_caller]
22449fn assert_hunk_revert(
22450    not_reverted_text_with_selections: &str,
22451    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22452    expected_reverted_text_with_selections: &str,
22453    base_text: &str,
22454    cx: &mut EditorLspTestContext,
22455) {
22456    cx.set_state(not_reverted_text_with_selections);
22457    cx.set_head_text(base_text);
22458    cx.executor().run_until_parked();
22459
22460    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22461        let snapshot = editor.snapshot(window, cx);
22462        let reverted_hunk_statuses = snapshot
22463            .buffer_snapshot
22464            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22465            .map(|hunk| hunk.status().kind)
22466            .collect::<Vec<_>>();
22467
22468        editor.git_restore(&Default::default(), window, cx);
22469        reverted_hunk_statuses
22470    });
22471    cx.executor().run_until_parked();
22472    cx.assert_editor_state(expected_reverted_text_with_selections);
22473    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22474}
22475
22476#[gpui::test(iterations = 10)]
22477async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22478    init_test(cx, |_| {});
22479
22480    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22481    let counter = diagnostic_requests.clone();
22482
22483    let fs = FakeFs::new(cx.executor());
22484    fs.insert_tree(
22485        path!("/a"),
22486        json!({
22487            "first.rs": "fn main() { let a = 5; }",
22488            "second.rs": "// Test file",
22489        }),
22490    )
22491    .await;
22492
22493    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22494    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22495    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22496
22497    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22498    language_registry.add(rust_lang());
22499    let mut fake_servers = language_registry.register_fake_lsp(
22500        "Rust",
22501        FakeLspAdapter {
22502            capabilities: lsp::ServerCapabilities {
22503                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22504                    lsp::DiagnosticOptions {
22505                        identifier: None,
22506                        inter_file_dependencies: true,
22507                        workspace_diagnostics: true,
22508                        work_done_progress_options: Default::default(),
22509                    },
22510                )),
22511                ..Default::default()
22512            },
22513            ..Default::default()
22514        },
22515    );
22516
22517    let editor = workspace
22518        .update(cx, |workspace, window, cx| {
22519            workspace.open_abs_path(
22520                PathBuf::from(path!("/a/first.rs")),
22521                OpenOptions::default(),
22522                window,
22523                cx,
22524            )
22525        })
22526        .unwrap()
22527        .await
22528        .unwrap()
22529        .downcast::<Editor>()
22530        .unwrap();
22531    let fake_server = fake_servers.next().await.unwrap();
22532    let server_id = fake_server.server.server_id();
22533    let mut first_request = fake_server
22534        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22535            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22536            let result_id = Some(new_result_id.to_string());
22537            assert_eq!(
22538                params.text_document.uri,
22539                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22540            );
22541            async move {
22542                Ok(lsp::DocumentDiagnosticReportResult::Report(
22543                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22544                        related_documents: None,
22545                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22546                            items: Vec::new(),
22547                            result_id,
22548                        },
22549                    }),
22550                ))
22551            }
22552        });
22553
22554    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22555        project.update(cx, |project, cx| {
22556            let buffer_id = editor
22557                .read(cx)
22558                .buffer()
22559                .read(cx)
22560                .as_singleton()
22561                .expect("created a singleton buffer")
22562                .read(cx)
22563                .remote_id();
22564            let buffer_result_id = project
22565                .lsp_store()
22566                .read(cx)
22567                .result_id(server_id, buffer_id, cx);
22568            assert_eq!(expected, buffer_result_id);
22569        });
22570    };
22571
22572    ensure_result_id(None, cx);
22573    cx.executor().advance_clock(Duration::from_millis(60));
22574    cx.executor().run_until_parked();
22575    assert_eq!(
22576        diagnostic_requests.load(atomic::Ordering::Acquire),
22577        1,
22578        "Opening file should trigger diagnostic request"
22579    );
22580    first_request
22581        .next()
22582        .await
22583        .expect("should have sent the first diagnostics pull request");
22584    ensure_result_id(Some("1".to_string()), cx);
22585
22586    // Editing should trigger diagnostics
22587    editor.update_in(cx, |editor, window, cx| {
22588        editor.handle_input("2", window, cx)
22589    });
22590    cx.executor().advance_clock(Duration::from_millis(60));
22591    cx.executor().run_until_parked();
22592    assert_eq!(
22593        diagnostic_requests.load(atomic::Ordering::Acquire),
22594        2,
22595        "Editing should trigger diagnostic request"
22596    );
22597    ensure_result_id(Some("2".to_string()), cx);
22598
22599    // Moving cursor should not trigger diagnostic request
22600    editor.update_in(cx, |editor, window, cx| {
22601        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22602            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22603        });
22604    });
22605    cx.executor().advance_clock(Duration::from_millis(60));
22606    cx.executor().run_until_parked();
22607    assert_eq!(
22608        diagnostic_requests.load(atomic::Ordering::Acquire),
22609        2,
22610        "Cursor movement should not trigger diagnostic request"
22611    );
22612    ensure_result_id(Some("2".to_string()), cx);
22613    // Multiple rapid edits should be debounced
22614    for _ in 0..5 {
22615        editor.update_in(cx, |editor, window, cx| {
22616            editor.handle_input("x", window, cx)
22617        });
22618    }
22619    cx.executor().advance_clock(Duration::from_millis(60));
22620    cx.executor().run_until_parked();
22621
22622    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22623    assert!(
22624        final_requests <= 4,
22625        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22626    );
22627    ensure_result_id(Some(final_requests.to_string()), cx);
22628}
22629
22630#[gpui::test]
22631async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22632    // Regression test for issue #11671
22633    // Previously, adding a cursor after moving multiple cursors would reset
22634    // the cursor count instead of adding to the existing cursors.
22635    init_test(cx, |_| {});
22636    let mut cx = EditorTestContext::new(cx).await;
22637
22638    // Create a simple buffer with cursor at start
22639    cx.set_state(indoc! {"
22640        ˇaaaa
22641        bbbb
22642        cccc
22643        dddd
22644        eeee
22645        ffff
22646        gggg
22647        hhhh"});
22648
22649    // Add 2 cursors below (so we have 3 total)
22650    cx.update_editor(|editor, window, cx| {
22651        editor.add_selection_below(&Default::default(), window, cx);
22652        editor.add_selection_below(&Default::default(), window, cx);
22653    });
22654
22655    // Verify we have 3 cursors
22656    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22657    assert_eq!(
22658        initial_count, 3,
22659        "Should have 3 cursors after adding 2 below"
22660    );
22661
22662    // Move down one line
22663    cx.update_editor(|editor, window, cx| {
22664        editor.move_down(&MoveDown, window, cx);
22665    });
22666
22667    // Add another cursor below
22668    cx.update_editor(|editor, window, cx| {
22669        editor.add_selection_below(&Default::default(), window, cx);
22670    });
22671
22672    // Should now have 4 cursors (3 original + 1 new)
22673    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22674    assert_eq!(
22675        final_count, 4,
22676        "Should have 4 cursors after moving and adding another"
22677    );
22678}
22679
22680#[gpui::test(iterations = 10)]
22681async fn test_document_colors(cx: &mut TestAppContext) {
22682    let expected_color = Rgba {
22683        r: 0.33,
22684        g: 0.33,
22685        b: 0.33,
22686        a: 0.33,
22687    };
22688
22689    init_test(cx, |_| {});
22690
22691    let fs = FakeFs::new(cx.executor());
22692    fs.insert_tree(
22693        path!("/a"),
22694        json!({
22695            "first.rs": "fn main() { let a = 5; }",
22696        }),
22697    )
22698    .await;
22699
22700    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22701    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22702    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22703
22704    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22705    language_registry.add(rust_lang());
22706    let mut fake_servers = language_registry.register_fake_lsp(
22707        "Rust",
22708        FakeLspAdapter {
22709            capabilities: lsp::ServerCapabilities {
22710                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22711                ..lsp::ServerCapabilities::default()
22712            },
22713            name: "rust-analyzer",
22714            ..FakeLspAdapter::default()
22715        },
22716    );
22717    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22718        "Rust",
22719        FakeLspAdapter {
22720            capabilities: lsp::ServerCapabilities {
22721                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22722                ..lsp::ServerCapabilities::default()
22723            },
22724            name: "not-rust-analyzer",
22725            ..FakeLspAdapter::default()
22726        },
22727    );
22728
22729    let editor = workspace
22730        .update(cx, |workspace, window, cx| {
22731            workspace.open_abs_path(
22732                PathBuf::from(path!("/a/first.rs")),
22733                OpenOptions::default(),
22734                window,
22735                cx,
22736            )
22737        })
22738        .unwrap()
22739        .await
22740        .unwrap()
22741        .downcast::<Editor>()
22742        .unwrap();
22743    let fake_language_server = fake_servers.next().await.unwrap();
22744    let fake_language_server_without_capabilities =
22745        fake_servers_without_capabilities.next().await.unwrap();
22746    let requests_made = Arc::new(AtomicUsize::new(0));
22747    let closure_requests_made = Arc::clone(&requests_made);
22748    let mut color_request_handle = fake_language_server
22749        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22750            let requests_made = Arc::clone(&closure_requests_made);
22751            async move {
22752                assert_eq!(
22753                    params.text_document.uri,
22754                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22755                );
22756                requests_made.fetch_add(1, atomic::Ordering::Release);
22757                Ok(vec![
22758                    lsp::ColorInformation {
22759                        range: lsp::Range {
22760                            start: lsp::Position {
22761                                line: 0,
22762                                character: 0,
22763                            },
22764                            end: lsp::Position {
22765                                line: 0,
22766                                character: 1,
22767                            },
22768                        },
22769                        color: lsp::Color {
22770                            red: 0.33,
22771                            green: 0.33,
22772                            blue: 0.33,
22773                            alpha: 0.33,
22774                        },
22775                    },
22776                    lsp::ColorInformation {
22777                        range: lsp::Range {
22778                            start: lsp::Position {
22779                                line: 0,
22780                                character: 0,
22781                            },
22782                            end: lsp::Position {
22783                                line: 0,
22784                                character: 1,
22785                            },
22786                        },
22787                        color: lsp::Color {
22788                            red: 0.33,
22789                            green: 0.33,
22790                            blue: 0.33,
22791                            alpha: 0.33,
22792                        },
22793                    },
22794                ])
22795            }
22796        });
22797
22798    let _handle = fake_language_server_without_capabilities
22799        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22800            panic!("Should not be called");
22801        });
22802    cx.executor().advance_clock(Duration::from_millis(100));
22803    color_request_handle.next().await.unwrap();
22804    cx.run_until_parked();
22805    assert_eq!(
22806        1,
22807        requests_made.load(atomic::Ordering::Acquire),
22808        "Should query for colors once per editor open"
22809    );
22810    editor.update_in(cx, |editor, _, cx| {
22811        assert_eq!(
22812            vec![expected_color],
22813            extract_color_inlays(editor, cx),
22814            "Should have an initial inlay"
22815        );
22816    });
22817
22818    // opening another file in a split should not influence the LSP query counter
22819    workspace
22820        .update(cx, |workspace, window, cx| {
22821            assert_eq!(
22822                workspace.panes().len(),
22823                1,
22824                "Should have one pane with one editor"
22825            );
22826            workspace.move_item_to_pane_in_direction(
22827                &MoveItemToPaneInDirection {
22828                    direction: SplitDirection::Right,
22829                    focus: false,
22830                    clone: true,
22831                },
22832                window,
22833                cx,
22834            );
22835        })
22836        .unwrap();
22837    cx.run_until_parked();
22838    workspace
22839        .update(cx, |workspace, _, cx| {
22840            let panes = workspace.panes();
22841            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
22842            for pane in panes {
22843                let editor = pane
22844                    .read(cx)
22845                    .active_item()
22846                    .and_then(|item| item.downcast::<Editor>())
22847                    .expect("Should have opened an editor in each split");
22848                let editor_file = editor
22849                    .read(cx)
22850                    .buffer()
22851                    .read(cx)
22852                    .as_singleton()
22853                    .expect("test deals with singleton buffers")
22854                    .read(cx)
22855                    .file()
22856                    .expect("test buffese should have a file")
22857                    .path();
22858                assert_eq!(
22859                    editor_file.as_ref(),
22860                    Path::new("first.rs"),
22861                    "Both editors should be opened for the same file"
22862                )
22863            }
22864        })
22865        .unwrap();
22866
22867    cx.executor().advance_clock(Duration::from_millis(500));
22868    let save = editor.update_in(cx, |editor, window, cx| {
22869        editor.move_to_end(&MoveToEnd, window, cx);
22870        editor.handle_input("dirty", window, cx);
22871        editor.save(
22872            SaveOptions {
22873                format: true,
22874                autosave: true,
22875            },
22876            project.clone(),
22877            window,
22878            cx,
22879        )
22880    });
22881    save.await.unwrap();
22882
22883    color_request_handle.next().await.unwrap();
22884    cx.run_until_parked();
22885    assert_eq!(
22886        3,
22887        requests_made.load(atomic::Ordering::Acquire),
22888        "Should query for colors once per save and once per formatting after save"
22889    );
22890
22891    drop(editor);
22892    let close = workspace
22893        .update(cx, |workspace, window, cx| {
22894            workspace.active_pane().update(cx, |pane, cx| {
22895                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22896            })
22897        })
22898        .unwrap();
22899    close.await.unwrap();
22900    let close = workspace
22901        .update(cx, |workspace, window, cx| {
22902            workspace.active_pane().update(cx, |pane, cx| {
22903                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22904            })
22905        })
22906        .unwrap();
22907    close.await.unwrap();
22908    assert_eq!(
22909        3,
22910        requests_made.load(atomic::Ordering::Acquire),
22911        "After saving and closing all editors, no extra requests should be made"
22912    );
22913    workspace
22914        .update(cx, |workspace, _, cx| {
22915            assert!(
22916                workspace.active_item(cx).is_none(),
22917                "Should close all editors"
22918            )
22919        })
22920        .unwrap();
22921
22922    workspace
22923        .update(cx, |workspace, window, cx| {
22924            workspace.active_pane().update(cx, |pane, cx| {
22925                pane.navigate_backward(window, cx);
22926            })
22927        })
22928        .unwrap();
22929    cx.executor().advance_clock(Duration::from_millis(100));
22930    cx.run_until_parked();
22931    let editor = workspace
22932        .update(cx, |workspace, _, cx| {
22933            workspace
22934                .active_item(cx)
22935                .expect("Should have reopened the editor again after navigating back")
22936                .downcast::<Editor>()
22937                .expect("Should be an editor")
22938        })
22939        .unwrap();
22940    color_request_handle.next().await.unwrap();
22941    assert_eq!(
22942        3,
22943        requests_made.load(atomic::Ordering::Acquire),
22944        "Cache should be reused on buffer close and reopen"
22945    );
22946    editor.update(cx, |editor, cx| {
22947        assert_eq!(
22948            vec![expected_color],
22949            extract_color_inlays(editor, cx),
22950            "Should have an initial inlay"
22951        );
22952    });
22953}
22954
22955#[gpui::test]
22956async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
22957    init_test(cx, |_| {});
22958    let (editor, cx) = cx.add_window_view(Editor::single_line);
22959    editor.update_in(cx, |editor, window, cx| {
22960        editor.set_text("oops\n\nwow\n", window, cx)
22961    });
22962    cx.run_until_parked();
22963    editor.update(cx, |editor, cx| {
22964        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
22965    });
22966    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
22967    cx.run_until_parked();
22968    editor.update(cx, |editor, cx| {
22969        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
22970    });
22971}
22972
22973#[track_caller]
22974fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22975    editor
22976        .all_inlays(cx)
22977        .into_iter()
22978        .filter_map(|inlay| inlay.get_color())
22979        .map(Rgba::from)
22980        .collect()
22981}