editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_manipulate_text(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    // Test convert_to_upper_case()
 4734    cx.set_state(indoc! {"
 4735        «hello worldˇ»
 4736    "});
 4737    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4738    cx.assert_editor_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741
 4742    // Test convert_to_lower_case()
 4743    cx.set_state(indoc! {"
 4744        «HELLO WORLDˇ»
 4745    "});
 4746    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4747    cx.assert_editor_state(indoc! {"
 4748        «hello worldˇ»
 4749    "});
 4750
 4751    // Test multiple line, single selection case
 4752    cx.set_state(indoc! {"
 4753        «The quick brown
 4754        fox jumps over
 4755        the lazy dogˇ»
 4756    "});
 4757    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4758    cx.assert_editor_state(indoc! {"
 4759        «The Quick Brown
 4760        Fox Jumps Over
 4761        The Lazy Dogˇ»
 4762    "});
 4763
 4764    // Test multiple line, single selection case
 4765    cx.set_state(indoc! {"
 4766        «The quick brown
 4767        fox jumps over
 4768        the lazy dogˇ»
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «TheQuickBrown
 4775        FoxJumpsOver
 4776        TheLazyDogˇ»
 4777    "});
 4778
 4779    // From here on out, test more complex cases of manipulate_text()
 4780
 4781    // Test no selection case - should affect words cursors are in
 4782    // Cursor at beginning, middle, and end of word
 4783    cx.set_state(indoc! {"
 4784        ˇhello big beauˇtiful worldˇ
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4789    "});
 4790
 4791    // Test multiple selections on a single line and across multiple lines
 4792    cx.set_state(indoc! {"
 4793        «Theˇ» quick «brown
 4794        foxˇ» jumps «overˇ»
 4795        the «lazyˇ» dog
 4796    "});
 4797    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4798    cx.assert_editor_state(indoc! {"
 4799        «THEˇ» quick «BROWN
 4800        FOXˇ» jumps «OVERˇ»
 4801        the «LAZYˇ» dog
 4802    "});
 4803
 4804    // Test case where text length grows
 4805    cx.set_state(indoc! {"
 4806        «tschüߡ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «TSCHÜSSˇ»
 4811    "});
 4812
 4813    // Test to make sure we don't crash when text shrinks
 4814    cx.set_state(indoc! {"
 4815        aaa_bbbˇ
 4816    "});
 4817    cx.update_editor(|e, window, cx| {
 4818        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4819    });
 4820    cx.assert_editor_state(indoc! {"
 4821        «aaaBbbˇ»
 4822    "});
 4823
 4824    // Test to make sure we all aware of the fact that each word can grow and shrink
 4825    // Final selections should be aware of this fact
 4826    cx.set_state(indoc! {"
 4827        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4834    "});
 4835
 4836    cx.set_state(indoc! {"
 4837        «hElLo, WoRld!ˇ»
 4838    "});
 4839    cx.update_editor(|e, window, cx| {
 4840        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4841    });
 4842    cx.assert_editor_state(indoc! {"
 4843        «HeLlO, wOrLD!ˇ»
 4844    "});
 4845}
 4846
 4847#[gpui::test]
 4848fn test_duplicate_line(cx: &mut TestAppContext) {
 4849    init_test(cx, |_| {});
 4850
 4851    let editor = cx.add_window(|window, cx| {
 4852        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4853        build_editor(buffer, window, cx)
 4854    });
 4855    _ = editor.update(cx, |editor, window, cx| {
 4856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4857            s.select_display_ranges([
 4858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4859                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4860                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4861                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4862            ])
 4863        });
 4864        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4865        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4866        assert_eq!(
 4867            editor.selections.display_ranges(cx),
 4868            vec![
 4869                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4870                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4871                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4872                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4873            ]
 4874        );
 4875    });
 4876
 4877    let editor = cx.add_window(|window, cx| {
 4878        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4879        build_editor(buffer, window, cx)
 4880    });
 4881    _ = editor.update(cx, |editor, window, cx| {
 4882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4883            s.select_display_ranges([
 4884                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4885                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4886            ])
 4887        });
 4888        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4889        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4890        assert_eq!(
 4891            editor.selections.display_ranges(cx),
 4892            vec![
 4893                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4894                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4895            ]
 4896        );
 4897    });
 4898
 4899    // With `move_upwards` the selections stay in place, except for
 4900    // the lines inserted above them
 4901    let editor = cx.add_window(|window, cx| {
 4902        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4903        build_editor(buffer, window, cx)
 4904    });
 4905    _ = editor.update(cx, |editor, window, cx| {
 4906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4907            s.select_display_ranges([
 4908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4909                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4910                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4911                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4912            ])
 4913        });
 4914        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4915        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4916        assert_eq!(
 4917            editor.selections.display_ranges(cx),
 4918            vec![
 4919                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4920                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4921                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4922                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4923            ]
 4924        );
 4925    });
 4926
 4927    let editor = cx.add_window(|window, cx| {
 4928        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4929        build_editor(buffer, window, cx)
 4930    });
 4931    _ = editor.update(cx, |editor, window, cx| {
 4932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4933            s.select_display_ranges([
 4934                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4935                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4936            ])
 4937        });
 4938        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4939        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4940        assert_eq!(
 4941            editor.selections.display_ranges(cx),
 4942            vec![
 4943                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4944                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4945            ]
 4946        );
 4947    });
 4948
 4949    let editor = cx.add_window(|window, cx| {
 4950        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4951        build_editor(buffer, window, cx)
 4952    });
 4953    _ = editor.update(cx, |editor, window, cx| {
 4954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4955            s.select_display_ranges([
 4956                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4957                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4958            ])
 4959        });
 4960        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4961        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4962        assert_eq!(
 4963            editor.selections.display_ranges(cx),
 4964            vec![
 4965                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4966                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4967            ]
 4968        );
 4969    });
 4970}
 4971
 4972#[gpui::test]
 4973fn test_move_line_up_down(cx: &mut TestAppContext) {
 4974    init_test(cx, |_| {});
 4975
 4976    let editor = cx.add_window(|window, cx| {
 4977        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4978        build_editor(buffer, window, cx)
 4979    });
 4980    _ = editor.update(cx, |editor, window, cx| {
 4981        editor.fold_creases(
 4982            vec![
 4983                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4984                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4985                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4986            ],
 4987            true,
 4988            window,
 4989            cx,
 4990        );
 4991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4992            s.select_display_ranges([
 4993                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4994                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4995                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4997            ])
 4998        });
 4999        assert_eq!(
 5000            editor.display_text(cx),
 5001            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5002        );
 5003
 5004        editor.move_line_up(&MoveLineUp, window, cx);
 5005        assert_eq!(
 5006            editor.display_text(cx),
 5007            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5008        );
 5009        assert_eq!(
 5010            editor.selections.display_ranges(cx),
 5011            vec![
 5012                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5013                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5014                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5015                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5016            ]
 5017        );
 5018    });
 5019
 5020    _ = editor.update(cx, |editor, window, cx| {
 5021        editor.move_line_down(&MoveLineDown, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5030                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5031                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5032                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_up(&MoveLineUp, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5064                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5065                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5066                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5067            ]
 5068        );
 5069    });
 5070}
 5071
 5072#[gpui::test]
 5073fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5074    init_test(cx, |_| {});
 5075    let editor = cx.add_window(|window, cx| {
 5076        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5077        build_editor(buffer, window, cx)
 5078    });
 5079    _ = editor.update(cx, |editor, window, cx| {
 5080        editor.fold_creases(
 5081            vec![Crease::simple(
 5082                Point::new(6, 4)..Point::new(7, 4),
 5083                FoldPlaceholder::test(),
 5084            )],
 5085            true,
 5086            window,
 5087            cx,
 5088        );
 5089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5090            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5091        });
 5092        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5093        editor.move_line_up(&MoveLineUp, window, cx);
 5094        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5095        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5096    });
 5097}
 5098
 5099#[gpui::test]
 5100fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5101    init_test(cx, |_| {});
 5102
 5103    let editor = cx.add_window(|window, cx| {
 5104        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5105        build_editor(buffer, window, cx)
 5106    });
 5107    _ = editor.update(cx, |editor, window, cx| {
 5108        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5109        editor.insert_blocks(
 5110            [BlockProperties {
 5111                style: BlockStyle::Fixed,
 5112                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5113                height: Some(1),
 5114                render: Arc::new(|_| div().into_any()),
 5115                priority: 0,
 5116            }],
 5117            Some(Autoscroll::fit()),
 5118            cx,
 5119        );
 5120        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5121            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5122        });
 5123        editor.move_line_down(&MoveLineDown, window, cx);
 5124    });
 5125}
 5126
 5127#[gpui::test]
 5128async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5129    init_test(cx, |_| {});
 5130
 5131    let mut cx = EditorTestContext::new(cx).await;
 5132    cx.set_state(
 5133        &"
 5134            ˇzero
 5135            one
 5136            two
 5137            three
 5138            four
 5139            five
 5140        "
 5141        .unindent(),
 5142    );
 5143
 5144    // Create a four-line block that replaces three lines of text.
 5145    cx.update_editor(|editor, window, cx| {
 5146        let snapshot = editor.snapshot(window, cx);
 5147        let snapshot = &snapshot.buffer_snapshot;
 5148        let placement = BlockPlacement::Replace(
 5149            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5150        );
 5151        editor.insert_blocks(
 5152            [BlockProperties {
 5153                placement,
 5154                height: Some(4),
 5155                style: BlockStyle::Sticky,
 5156                render: Arc::new(|_| gpui::div().into_any_element()),
 5157                priority: 0,
 5158            }],
 5159            None,
 5160            cx,
 5161        );
 5162    });
 5163
 5164    // Move down so that the cursor touches the block.
 5165    cx.update_editor(|editor, window, cx| {
 5166        editor.move_down(&Default::default(), window, cx);
 5167    });
 5168    cx.assert_editor_state(
 5169        &"
 5170            zero
 5171            «one
 5172            two
 5173            threeˇ»
 5174            four
 5175            five
 5176        "
 5177        .unindent(),
 5178    );
 5179
 5180    // Move down past the block.
 5181    cx.update_editor(|editor, window, cx| {
 5182        editor.move_down(&Default::default(), window, cx);
 5183    });
 5184    cx.assert_editor_state(
 5185        &"
 5186            zero
 5187            one
 5188            two
 5189            three
 5190            ˇfour
 5191            five
 5192        "
 5193        .unindent(),
 5194    );
 5195}
 5196
 5197#[gpui::test]
 5198fn test_transpose(cx: &mut TestAppContext) {
 5199    init_test(cx, |_| {});
 5200
 5201    _ = cx.add_window(|window, cx| {
 5202        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5203        editor.set_style(EditorStyle::default(), window, cx);
 5204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5205            s.select_ranges([1..1])
 5206        });
 5207        editor.transpose(&Default::default(), window, cx);
 5208        assert_eq!(editor.text(cx), "bac");
 5209        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5210
 5211        editor.transpose(&Default::default(), window, cx);
 5212        assert_eq!(editor.text(cx), "bca");
 5213        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5214
 5215        editor.transpose(&Default::default(), window, cx);
 5216        assert_eq!(editor.text(cx), "bac");
 5217        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5218
 5219        editor
 5220    });
 5221
 5222    _ = cx.add_window(|window, cx| {
 5223        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5224        editor.set_style(EditorStyle::default(), window, cx);
 5225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5226            s.select_ranges([3..3])
 5227        });
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "acb\nde");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5231
 5232        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5233            s.select_ranges([4..4])
 5234        });
 5235        editor.transpose(&Default::default(), window, cx);
 5236        assert_eq!(editor.text(cx), "acbd\ne");
 5237        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5238
 5239        editor.transpose(&Default::default(), window, cx);
 5240        assert_eq!(editor.text(cx), "acbde\n");
 5241        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5242
 5243        editor.transpose(&Default::default(), window, cx);
 5244        assert_eq!(editor.text(cx), "acbd\ne");
 5245        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5246
 5247        editor
 5248    });
 5249
 5250    _ = cx.add_window(|window, cx| {
 5251        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5252        editor.set_style(EditorStyle::default(), window, cx);
 5253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5254            s.select_ranges([1..1, 2..2, 4..4])
 5255        });
 5256        editor.transpose(&Default::default(), window, cx);
 5257        assert_eq!(editor.text(cx), "bacd\ne");
 5258        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5259
 5260        editor.transpose(&Default::default(), window, cx);
 5261        assert_eq!(editor.text(cx), "bcade\n");
 5262        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5263
 5264        editor.transpose(&Default::default(), window, cx);
 5265        assert_eq!(editor.text(cx), "bcda\ne");
 5266        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5267
 5268        editor.transpose(&Default::default(), window, cx);
 5269        assert_eq!(editor.text(cx), "bcade\n");
 5270        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5271
 5272        editor.transpose(&Default::default(), window, cx);
 5273        assert_eq!(editor.text(cx), "bcaed\n");
 5274        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5275
 5276        editor
 5277    });
 5278
 5279    _ = cx.add_window(|window, cx| {
 5280        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5281        editor.set_style(EditorStyle::default(), window, cx);
 5282        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5283            s.select_ranges([4..4])
 5284        });
 5285        editor.transpose(&Default::default(), window, cx);
 5286        assert_eq!(editor.text(cx), "🏀🍐✋");
 5287        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5288
 5289        editor.transpose(&Default::default(), window, cx);
 5290        assert_eq!(editor.text(cx), "🏀✋🍐");
 5291        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5292
 5293        editor.transpose(&Default::default(), window, cx);
 5294        assert_eq!(editor.text(cx), "🏀🍐✋");
 5295        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5296
 5297        editor
 5298    });
 5299}
 5300
 5301#[gpui::test]
 5302async fn test_rewrap(cx: &mut TestAppContext) {
 5303    init_test(cx, |settings| {
 5304        settings.languages.0.extend([
 5305            (
 5306                "Markdown".into(),
 5307                LanguageSettingsContent {
 5308                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5309                    preferred_line_length: Some(40),
 5310                    ..Default::default()
 5311                },
 5312            ),
 5313            (
 5314                "Plain Text".into(),
 5315                LanguageSettingsContent {
 5316                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5317                    preferred_line_length: Some(40),
 5318                    ..Default::default()
 5319                },
 5320            ),
 5321            (
 5322                "C++".into(),
 5323                LanguageSettingsContent {
 5324                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5325                    preferred_line_length: Some(40),
 5326                    ..Default::default()
 5327                },
 5328            ),
 5329            (
 5330                "Python".into(),
 5331                LanguageSettingsContent {
 5332                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5333                    preferred_line_length: Some(40),
 5334                    ..Default::default()
 5335                },
 5336            ),
 5337            (
 5338                "Rust".into(),
 5339                LanguageSettingsContent {
 5340                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5341                    preferred_line_length: Some(40),
 5342                    ..Default::default()
 5343                },
 5344            ),
 5345        ])
 5346    });
 5347
 5348    let mut cx = EditorTestContext::new(cx).await;
 5349
 5350    let cpp_language = Arc::new(Language::new(
 5351        LanguageConfig {
 5352            name: "C++".into(),
 5353            line_comments: vec!["// ".into()],
 5354            ..LanguageConfig::default()
 5355        },
 5356        None,
 5357    ));
 5358    let python_language = Arc::new(Language::new(
 5359        LanguageConfig {
 5360            name: "Python".into(),
 5361            line_comments: vec!["# ".into()],
 5362            ..LanguageConfig::default()
 5363        },
 5364        None,
 5365    ));
 5366    let markdown_language = Arc::new(Language::new(
 5367        LanguageConfig {
 5368            name: "Markdown".into(),
 5369            rewrap_prefixes: vec![
 5370                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5371                regex::Regex::new("[-*+]\\s+").unwrap(),
 5372            ],
 5373            ..LanguageConfig::default()
 5374        },
 5375        None,
 5376    ));
 5377    let rust_language = Arc::new(Language::new(
 5378        LanguageConfig {
 5379            name: "Rust".into(),
 5380            line_comments: vec!["// ".into(), "/// ".into()],
 5381            ..LanguageConfig::default()
 5382        },
 5383        Some(tree_sitter_rust::LANGUAGE.into()),
 5384    ));
 5385
 5386    let plaintext_language = Arc::new(Language::new(
 5387        LanguageConfig {
 5388            name: "Plain Text".into(),
 5389            ..LanguageConfig::default()
 5390        },
 5391        None,
 5392    ));
 5393
 5394    // Test basic rewrapping of a long line with a cursor
 5395    assert_rewrap(
 5396        indoc! {"
 5397            // ˇThis is a long comment that needs to be wrapped.
 5398        "},
 5399        indoc! {"
 5400            // ˇThis is a long comment that needs to
 5401            // be wrapped.
 5402        "},
 5403        cpp_language.clone(),
 5404        &mut cx,
 5405    );
 5406
 5407    // Test rewrapping a full selection
 5408    assert_rewrap(
 5409        indoc! {"
 5410            «// This selected long comment needs to be wrapped.ˇ»"
 5411        },
 5412        indoc! {"
 5413            «// This selected long comment needs to
 5414            // be wrapped.ˇ»"
 5415        },
 5416        cpp_language.clone(),
 5417        &mut cx,
 5418    );
 5419
 5420    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5421    assert_rewrap(
 5422        indoc! {"
 5423            // ˇThis is the first line.
 5424            // Thisˇ is the second line.
 5425            // This is the thirdˇ line, all part of one paragraph.
 5426         "},
 5427        indoc! {"
 5428            // ˇThis is the first line. Thisˇ is the
 5429            // second line. This is the thirdˇ line,
 5430            // all part of one paragraph.
 5431         "},
 5432        cpp_language.clone(),
 5433        &mut cx,
 5434    );
 5435
 5436    // Test multiple cursors in different paragraphs trigger separate rewraps
 5437    assert_rewrap(
 5438        indoc! {"
 5439            // ˇThis is the first paragraph, first line.
 5440            // ˇThis is the first paragraph, second line.
 5441
 5442            // ˇThis is the second paragraph, first line.
 5443            // ˇThis is the second paragraph, second line.
 5444        "},
 5445        indoc! {"
 5446            // ˇThis is the first paragraph, first
 5447            // line. ˇThis is the first paragraph,
 5448            // second line.
 5449
 5450            // ˇThis is the second paragraph, first
 5451            // line. ˇThis is the second paragraph,
 5452            // second line.
 5453        "},
 5454        cpp_language.clone(),
 5455        &mut cx,
 5456    );
 5457
 5458    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5459    assert_rewrap(
 5460        indoc! {"
 5461            «// A regular long long comment to be wrapped.
 5462            /// A documentation long comment to be wrapped.ˇ»
 5463          "},
 5464        indoc! {"
 5465            «// A regular long long comment to be
 5466            // wrapped.
 5467            /// A documentation long comment to be
 5468            /// wrapped.ˇ»
 5469          "},
 5470        rust_language.clone(),
 5471        &mut cx,
 5472    );
 5473
 5474    // Test that change in indentation level trigger seperate rewraps
 5475    assert_rewrap(
 5476        indoc! {"
 5477            fn foo() {
 5478                «// This is a long comment at the base indent.
 5479                    // This is a long comment at the next indent.ˇ»
 5480            }
 5481        "},
 5482        indoc! {"
 5483            fn foo() {
 5484                «// This is a long comment at the
 5485                // base indent.
 5486                    // This is a long comment at the
 5487                    // next indent.ˇ»
 5488            }
 5489        "},
 5490        rust_language.clone(),
 5491        &mut cx,
 5492    );
 5493
 5494    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5495    assert_rewrap(
 5496        indoc! {"
 5497            # ˇThis is a long comment using a pound sign.
 5498        "},
 5499        indoc! {"
 5500            # ˇThis is a long comment using a pound
 5501            # sign.
 5502        "},
 5503        python_language.clone(),
 5504        &mut cx,
 5505    );
 5506
 5507    // Test rewrapping only affects comments, not code even when selected
 5508    assert_rewrap(
 5509        indoc! {"
 5510            «/// This doc comment is long and should be wrapped.
 5511            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5512        "},
 5513        indoc! {"
 5514            «/// This doc comment is long and should
 5515            /// be wrapped.
 5516            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5517        "},
 5518        rust_language.clone(),
 5519        &mut cx,
 5520    );
 5521
 5522    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5523    assert_rewrap(
 5524        indoc! {"
 5525            # Header
 5526
 5527            A long long long line of markdown text to wrap.ˇ
 5528         "},
 5529        indoc! {"
 5530            # Header
 5531
 5532            A long long long line of markdown text
 5533            to wrap.ˇ
 5534         "},
 5535        markdown_language.clone(),
 5536        &mut cx,
 5537    );
 5538
 5539    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5540    assert_rewrap(
 5541        indoc! {"
 5542            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5543            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5544            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5545        "},
 5546        indoc! {"
 5547            «1. This is a numbered list item that is
 5548               very long and needs to be wrapped
 5549               properly.
 5550            2. This is a numbered list item that is
 5551               very long and needs to be wrapped
 5552               properly.
 5553            - This is an unordered list item that is
 5554              also very long and should not merge
 5555              with the numbered item.ˇ»
 5556        "},
 5557        markdown_language.clone(),
 5558        &mut cx,
 5559    );
 5560
 5561    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5562    assert_rewrap(
 5563        indoc! {"
 5564            «1. This is a numbered list item that is
 5565            very long and needs to be wrapped
 5566            properly.
 5567            2. This is a numbered list item that is
 5568            very long and needs to be wrapped
 5569            properly.
 5570            - This is an unordered list item that is
 5571            also very long and should not merge with
 5572            the numbered item.ˇ»
 5573        "},
 5574        indoc! {"
 5575            «1. This is a numbered list item that is
 5576               very long and needs to be wrapped
 5577               properly.
 5578            2. This is a numbered list item that is
 5579               very long and needs to be wrapped
 5580               properly.
 5581            - This is an unordered list item that is
 5582              also very long and should not merge
 5583              with the numbered item.ˇ»
 5584        "},
 5585        markdown_language.clone(),
 5586        &mut cx,
 5587    );
 5588
 5589    // Test that rewrapping maintain indents even when they already exists.
 5590    assert_rewrap(
 5591        indoc! {"
 5592            «1. This is a numbered list
 5593               item that is very long and needs to be wrapped properly.
 5594            2. This is a numbered list
 5595               item that is very long and needs to be wrapped properly.
 5596            - This is an unordered list item that is also very long and
 5597              should not merge with the numbered item.ˇ»
 5598        "},
 5599        indoc! {"
 5600            «1. This is a numbered list item that is
 5601               very long and needs to be wrapped
 5602               properly.
 5603            2. This is a numbered list item that is
 5604               very long and needs to be wrapped
 5605               properly.
 5606            - This is an unordered list item that is
 5607              also very long and should not merge
 5608              with the numbered item.ˇ»
 5609        "},
 5610        markdown_language.clone(),
 5611        &mut cx,
 5612    );
 5613
 5614    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5615    assert_rewrap(
 5616        indoc! {"
 5617            ˇThis is a very long line of plain text that will be wrapped.
 5618        "},
 5619        indoc! {"
 5620            ˇThis is a very long line of plain text
 5621            that will be wrapped.
 5622        "},
 5623        plaintext_language.clone(),
 5624        &mut cx,
 5625    );
 5626
 5627    // Test that non-commented code acts as a paragraph boundary within a selection
 5628    assert_rewrap(
 5629        indoc! {"
 5630               «// This is the first long comment block to be wrapped.
 5631               fn my_func(a: u32);
 5632               // This is the second long comment block to be wrapped.ˇ»
 5633           "},
 5634        indoc! {"
 5635               «// This is the first long comment block
 5636               // to be wrapped.
 5637               fn my_func(a: u32);
 5638               // This is the second long comment block
 5639               // to be wrapped.ˇ»
 5640           "},
 5641        rust_language.clone(),
 5642        &mut cx,
 5643    );
 5644
 5645    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5646    assert_rewrap(
 5647        indoc! {"
 5648            «ˇThis is a very long line that will be wrapped.
 5649
 5650            This is another paragraph in the same selection.»
 5651
 5652            «\tThis is a very long indented line that will be wrapped.ˇ»
 5653         "},
 5654        indoc! {"
 5655            «ˇThis is a very long line that will be
 5656            wrapped.
 5657
 5658            This is another paragraph in the same
 5659            selection.»
 5660
 5661            «\tThis is a very long indented line
 5662            \tthat will be wrapped.ˇ»
 5663         "},
 5664        plaintext_language.clone(),
 5665        &mut cx,
 5666    );
 5667
 5668    // Test that an empty comment line acts as a paragraph boundary
 5669    assert_rewrap(
 5670        indoc! {"
 5671            // ˇThis is a long comment that will be wrapped.
 5672            //
 5673            // And this is another long comment that will also be wrapped.ˇ
 5674         "},
 5675        indoc! {"
 5676            // ˇThis is a long comment that will be
 5677            // wrapped.
 5678            //
 5679            // And this is another long comment that
 5680            // will also be wrapped.ˇ
 5681         "},
 5682        cpp_language,
 5683        &mut cx,
 5684    );
 5685
 5686    #[track_caller]
 5687    fn assert_rewrap(
 5688        unwrapped_text: &str,
 5689        wrapped_text: &str,
 5690        language: Arc<Language>,
 5691        cx: &mut EditorTestContext,
 5692    ) {
 5693        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5694        cx.set_state(unwrapped_text);
 5695        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5696        cx.assert_editor_state(wrapped_text);
 5697    }
 5698}
 5699
 5700#[gpui::test]
 5701async fn test_hard_wrap(cx: &mut TestAppContext) {
 5702    init_test(cx, |_| {});
 5703    let mut cx = EditorTestContext::new(cx).await;
 5704
 5705    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5706    cx.update_editor(|editor, _, cx| {
 5707        editor.set_hard_wrap(Some(14), cx);
 5708    });
 5709
 5710    cx.set_state(indoc!(
 5711        "
 5712        one two three ˇ
 5713        "
 5714    ));
 5715    cx.simulate_input("four");
 5716    cx.run_until_parked();
 5717
 5718    cx.assert_editor_state(indoc!(
 5719        "
 5720        one two three
 5721        fourˇ
 5722        "
 5723    ));
 5724
 5725    cx.update_editor(|editor, window, cx| {
 5726        editor.newline(&Default::default(), window, cx);
 5727    });
 5728    cx.run_until_parked();
 5729    cx.assert_editor_state(indoc!(
 5730        "
 5731        one two three
 5732        four
 5733        ˇ
 5734        "
 5735    ));
 5736
 5737    cx.simulate_input("five");
 5738    cx.run_until_parked();
 5739    cx.assert_editor_state(indoc!(
 5740        "
 5741        one two three
 5742        four
 5743        fiveˇ
 5744        "
 5745    ));
 5746
 5747    cx.update_editor(|editor, window, cx| {
 5748        editor.newline(&Default::default(), window, cx);
 5749    });
 5750    cx.run_until_parked();
 5751    cx.simulate_input("# ");
 5752    cx.run_until_parked();
 5753    cx.assert_editor_state(indoc!(
 5754        "
 5755        one two three
 5756        four
 5757        five
 5758        # ˇ
 5759        "
 5760    ));
 5761
 5762    cx.update_editor(|editor, window, cx| {
 5763        editor.newline(&Default::default(), window, cx);
 5764    });
 5765    cx.run_until_parked();
 5766    cx.assert_editor_state(indoc!(
 5767        "
 5768        one two three
 5769        four
 5770        five
 5771        #\x20
 5772 5773        "
 5774    ));
 5775
 5776    cx.simulate_input(" 6");
 5777    cx.run_until_parked();
 5778    cx.assert_editor_state(indoc!(
 5779        "
 5780        one two three
 5781        four
 5782        five
 5783        #
 5784        # 6ˇ
 5785        "
 5786    ));
 5787}
 5788
 5789#[gpui::test]
 5790async fn test_clipboard(cx: &mut TestAppContext) {
 5791    init_test(cx, |_| {});
 5792
 5793    let mut cx = EditorTestContext::new(cx).await;
 5794
 5795    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5796    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5797    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5798
 5799    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5800    cx.set_state("two ˇfour ˇsix ˇ");
 5801    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5802    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5803
 5804    // Paste again but with only two cursors. Since the number of cursors doesn't
 5805    // match the number of slices in the clipboard, the entire clipboard text
 5806    // is pasted at each cursor.
 5807    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5808    cx.update_editor(|e, window, cx| {
 5809        e.handle_input("( ", window, cx);
 5810        e.paste(&Paste, window, cx);
 5811        e.handle_input(") ", window, cx);
 5812    });
 5813    cx.assert_editor_state(
 5814        &([
 5815            "( one✅ ",
 5816            "three ",
 5817            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5818            "three ",
 5819            "five ) ˇ",
 5820        ]
 5821        .join("\n")),
 5822    );
 5823
 5824    // Cut with three selections, one of which is full-line.
 5825    cx.set_state(indoc! {"
 5826        1«2ˇ»3
 5827        4ˇ567
 5828        «8ˇ»9"});
 5829    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5830    cx.assert_editor_state(indoc! {"
 5831        1ˇ3
 5832        ˇ9"});
 5833
 5834    // Paste with three selections, noticing how the copied selection that was full-line
 5835    // gets inserted before the second cursor.
 5836    cx.set_state(indoc! {"
 5837        1ˇ3
 5838 5839        «oˇ»ne"});
 5840    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5841    cx.assert_editor_state(indoc! {"
 5842        12ˇ3
 5843        4567
 5844 5845        8ˇne"});
 5846
 5847    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5848    cx.set_state(indoc! {"
 5849        The quick brown
 5850        fox juˇmps over
 5851        the lazy dog"});
 5852    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5853    assert_eq!(
 5854        cx.read_from_clipboard()
 5855            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5856        Some("fox jumps over\n".to_string())
 5857    );
 5858
 5859    // Paste with three selections, noticing how the copied full-line selection is inserted
 5860    // before the empty selections but replaces the selection that is non-empty.
 5861    cx.set_state(indoc! {"
 5862        Tˇhe quick brown
 5863        «foˇ»x jumps over
 5864        tˇhe lazy dog"});
 5865    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5866    cx.assert_editor_state(indoc! {"
 5867        fox jumps over
 5868        Tˇhe quick brown
 5869        fox jumps over
 5870        ˇx jumps over
 5871        fox jumps over
 5872        tˇhe lazy dog"});
 5873}
 5874
 5875#[gpui::test]
 5876async fn test_copy_trim(cx: &mut TestAppContext) {
 5877    init_test(cx, |_| {});
 5878
 5879    let mut cx = EditorTestContext::new(cx).await;
 5880    cx.set_state(
 5881        r#"            «for selection 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                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5888            }
 5889        "#,
 5890    );
 5891    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5892    assert_eq!(
 5893        cx.read_from_clipboard()
 5894            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5895        Some(
 5896            "for selection in selections.iter() {
 5897            let mut start = selection.start;
 5898            let mut end = selection.end;
 5899            let is_entire_line = selection.is_empty();
 5900            if is_entire_line {
 5901                start = Point::new(start.row, 0);"
 5902                .to_string()
 5903        ),
 5904        "Regular copying preserves all indentation selected",
 5905    );
 5906    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5907    assert_eq!(
 5908        cx.read_from_clipboard()
 5909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5910        Some(
 5911            "for selection in selections.iter() {
 5912let mut start = selection.start;
 5913let mut end = selection.end;
 5914let is_entire_line = selection.is_empty();
 5915if is_entire_line {
 5916    start = Point::new(start.row, 0);"
 5917                .to_string()
 5918        ),
 5919        "Copying with stripping should strip all leading whitespaces"
 5920    );
 5921
 5922    cx.set_state(
 5923        r#"       «     for selection in selections.iter() {
 5924            let mut start = selection.start;
 5925            let mut end = selection.end;
 5926            let is_entire_line = selection.is_empty();
 5927            if is_entire_line {
 5928                start = Point::new(start.row, 0);ˇ»
 5929                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5930            }
 5931        "#,
 5932    );
 5933    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5934    assert_eq!(
 5935        cx.read_from_clipboard()
 5936            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5937        Some(
 5938            "     for selection in selections.iter() {
 5939            let mut start = selection.start;
 5940            let mut end = selection.end;
 5941            let is_entire_line = selection.is_empty();
 5942            if is_entire_line {
 5943                start = Point::new(start.row, 0);"
 5944                .to_string()
 5945        ),
 5946        "Regular copying preserves all indentation selected",
 5947    );
 5948    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5949    assert_eq!(
 5950        cx.read_from_clipboard()
 5951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5952        Some(
 5953            "for selection in selections.iter() {
 5954let mut start = selection.start;
 5955let mut end = selection.end;
 5956let is_entire_line = selection.is_empty();
 5957if is_entire_line {
 5958    start = Point::new(start.row, 0);"
 5959                .to_string()
 5960        ),
 5961        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5962    );
 5963
 5964    cx.set_state(
 5965        r#"       «ˇ     for selection in selections.iter() {
 5966            let mut start = selection.start;
 5967            let mut end = selection.end;
 5968            let is_entire_line = selection.is_empty();
 5969            if is_entire_line {
 5970                start = Point::new(start.row, 0);»
 5971                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5972            }
 5973        "#,
 5974    );
 5975    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5976    assert_eq!(
 5977        cx.read_from_clipboard()
 5978            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5979        Some(
 5980            "     for selection in selections.iter() {
 5981            let mut start = selection.start;
 5982            let mut end = selection.end;
 5983            let is_entire_line = selection.is_empty();
 5984            if is_entire_line {
 5985                start = Point::new(start.row, 0);"
 5986                .to_string()
 5987        ),
 5988        "Regular copying for reverse selection works the same",
 5989    );
 5990    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5991    assert_eq!(
 5992        cx.read_from_clipboard()
 5993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5994        Some(
 5995            "for selection in selections.iter() {
 5996let mut start = selection.start;
 5997let mut end = selection.end;
 5998let is_entire_line = selection.is_empty();
 5999if is_entire_line {
 6000    start = Point::new(start.row, 0);"
 6001                .to_string()
 6002        ),
 6003        "Copying with stripping for reverse selection works the same"
 6004    );
 6005
 6006    cx.set_state(
 6007        r#"            for selection «in selections.iter() {
 6008            let mut start = selection.start;
 6009            let mut end = selection.end;
 6010            let is_entire_line = selection.is_empty();
 6011            if is_entire_line {
 6012                start = Point::new(start.row, 0);ˇ»
 6013                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6014            }
 6015        "#,
 6016    );
 6017    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6018    assert_eq!(
 6019        cx.read_from_clipboard()
 6020            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6021        Some(
 6022            "in selections.iter() {
 6023            let mut start = selection.start;
 6024            let mut end = selection.end;
 6025            let is_entire_line = selection.is_empty();
 6026            if is_entire_line {
 6027                start = Point::new(start.row, 0);"
 6028                .to_string()
 6029        ),
 6030        "When selecting past the indent, the copying works as usual",
 6031    );
 6032    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6033    assert_eq!(
 6034        cx.read_from_clipboard()
 6035            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6036        Some(
 6037            "in selections.iter() {
 6038            let mut start = selection.start;
 6039            let mut end = selection.end;
 6040            let is_entire_line = selection.is_empty();
 6041            if is_entire_line {
 6042                start = Point::new(start.row, 0);"
 6043                .to_string()
 6044        ),
 6045        "When selecting past the indent, nothing is trimmed"
 6046    );
 6047
 6048    cx.set_state(
 6049        r#"            «for selection in selections.iter() {
 6050            let mut start = selection.start;
 6051
 6052            let mut end = selection.end;
 6053            let is_entire_line = selection.is_empty();
 6054            if is_entire_line {
 6055                start = Point::new(start.row, 0);
 6056ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6057            }
 6058        "#,
 6059    );
 6060    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6061    assert_eq!(
 6062        cx.read_from_clipboard()
 6063            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6064        Some(
 6065            "for selection in selections.iter() {
 6066let mut start = selection.start;
 6067
 6068let mut end = selection.end;
 6069let is_entire_line = selection.is_empty();
 6070if is_entire_line {
 6071    start = Point::new(start.row, 0);
 6072"
 6073            .to_string()
 6074        ),
 6075        "Copying with stripping should ignore empty lines"
 6076    );
 6077}
 6078
 6079#[gpui::test]
 6080async fn test_copy_entire_line(cx: &mut TestAppContext) {
 6081    init_test(cx, |_| {});
 6082
 6083    let mut cx = EditorTestContext::new(cx).await;
 6084
 6085    cx.set_state("line1\nline2\nlastˇ line");
 6086    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6087    assert_eq!(
 6088        cx.read_from_clipboard()
 6089            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6090        Some("last line\n".to_string()),
 6091        "Copying last line of file without newline should include trailing newline"
 6092    );
 6093
 6094    cx.set_state("line1\nˇline2\nlast line");
 6095    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6096    assert_eq!(
 6097        cx.read_from_clipboard()
 6098            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6099        Some("line2\n".to_string()),
 6100        "Copying a line without a selection should copy that line with a trailing newline"
 6101    );
 6102
 6103    cx.set_state("ˇ");
 6104    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6105    assert_eq!(
 6106        cx.read_from_clipboard()
 6107            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6108        Some("\n".to_string()),
 6109        "Copying empty line should be a newline"
 6110    );
 6111
 6112    cx.set_state("line1\nline2\nlast line\nˇ");
 6113    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6114    assert_eq!(
 6115        cx.read_from_clipboard()
 6116            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6117        Some("\n".to_string()),
 6118        "Copying empty line at end of file should be a newline"
 6119    );
 6120}
 6121
 6122#[gpui::test]
 6123async fn test_cut_entire_line(cx: &mut TestAppContext) {
 6124    init_test(cx, |_| {});
 6125
 6126    let mut cx = EditorTestContext::new(cx).await;
 6127
 6128    cx.set_state("line1\nline2\nlastˇ line");
 6129    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6130    assert_eq!(
 6131        cx.read_from_clipboard()
 6132            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6133        Some("last line\n".to_string()),
 6134        "Cutting last line of file without newline should include trailing newline"
 6135    );
 6136    cx.assert_editor_state("line1\nline2\nˇ");
 6137
 6138    cx.set_state("line1\nˇline2\nlast line");
 6139    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6140    assert_eq!(
 6141        cx.read_from_clipboard()
 6142            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6143        Some("line2\n".to_string()),
 6144        "Cutting a line without a selection should cut that line with a trailing newline"
 6145    );
 6146    cx.assert_editor_state("line1\nˇlast line");
 6147
 6148    cx.set_state("ˇ");
 6149    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6150    assert_eq!(
 6151        cx.read_from_clipboard()
 6152            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6153        Some("\n".to_string()),
 6154        "Cutting empty line should be a newline"
 6155    );
 6156    cx.assert_editor_state("ˇ");
 6157
 6158    cx.set_state("line1\nline2\nlast line\nˇ");
 6159    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6160    assert_eq!(
 6161        cx.read_from_clipboard()
 6162            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6163        Some("\n".to_string()),
 6164        "Cutting empty line at end of file should be a newline"
 6165    );
 6166    cx.assert_editor_state("line1\nline2\nlast lineˇ");
 6167}
 6168
 6169#[gpui::test]
 6170async fn test_paste_multiline(cx: &mut TestAppContext) {
 6171    init_test(cx, |_| {});
 6172
 6173    let mut cx = EditorTestContext::new(cx).await;
 6174    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6175
 6176    // Cut an indented block, without the leading whitespace.
 6177    cx.set_state(indoc! {"
 6178        const a: B = (
 6179            c(),
 6180            «d(
 6181                e,
 6182                f
 6183            )ˇ»
 6184        );
 6185    "});
 6186    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6187    cx.assert_editor_state(indoc! {"
 6188        const a: B = (
 6189            c(),
 6190            ˇ
 6191        );
 6192    "});
 6193
 6194    // Paste it at the same position.
 6195    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6196    cx.assert_editor_state(indoc! {"
 6197        const a: B = (
 6198            c(),
 6199            d(
 6200                e,
 6201                f
 6202 6203        );
 6204    "});
 6205
 6206    // Paste it at a line with a lower indent level.
 6207    cx.set_state(indoc! {"
 6208        ˇ
 6209        const a: B = (
 6210            c(),
 6211        );
 6212    "});
 6213    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6214    cx.assert_editor_state(indoc! {"
 6215        d(
 6216            e,
 6217            f
 6218 6219        const a: B = (
 6220            c(),
 6221        );
 6222    "});
 6223
 6224    // Cut an indented block, with the leading whitespace.
 6225    cx.set_state(indoc! {"
 6226        const a: B = (
 6227            c(),
 6228        «    d(
 6229                e,
 6230                f
 6231            )
 6232        ˇ»);
 6233    "});
 6234    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6235    cx.assert_editor_state(indoc! {"
 6236        const a: B = (
 6237            c(),
 6238        ˇ);
 6239    "});
 6240
 6241    // Paste it at the same position.
 6242    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6243    cx.assert_editor_state(indoc! {"
 6244        const a: B = (
 6245            c(),
 6246            d(
 6247                e,
 6248                f
 6249            )
 6250        ˇ);
 6251    "});
 6252
 6253    // Paste it at a line with a higher indent level.
 6254    cx.set_state(indoc! {"
 6255        const a: B = (
 6256            c(),
 6257            d(
 6258                e,
 6259 6260            )
 6261        );
 6262    "});
 6263    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6264    cx.assert_editor_state(indoc! {"
 6265        const a: B = (
 6266            c(),
 6267            d(
 6268                e,
 6269                f    d(
 6270                    e,
 6271                    f
 6272                )
 6273        ˇ
 6274            )
 6275        );
 6276    "});
 6277
 6278    // Copy an indented block, starting mid-line
 6279    cx.set_state(indoc! {"
 6280        const a: B = (
 6281            c(),
 6282            somethin«g(
 6283                e,
 6284                f
 6285            )ˇ»
 6286        );
 6287    "});
 6288    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6289
 6290    // Paste it on a line with a lower indent level
 6291    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6292    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6293    cx.assert_editor_state(indoc! {"
 6294        const a: B = (
 6295            c(),
 6296            something(
 6297                e,
 6298                f
 6299            )
 6300        );
 6301        g(
 6302            e,
 6303            f
 6304"});
 6305}
 6306
 6307#[gpui::test]
 6308async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6309    init_test(cx, |_| {});
 6310
 6311    cx.write_to_clipboard(ClipboardItem::new_string(
 6312        "    d(\n        e\n    );\n".into(),
 6313    ));
 6314
 6315    let mut cx = EditorTestContext::new(cx).await;
 6316    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6317
 6318    cx.set_state(indoc! {"
 6319        fn a() {
 6320            b();
 6321            if c() {
 6322                ˇ
 6323            }
 6324        }
 6325    "});
 6326
 6327    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6328    cx.assert_editor_state(indoc! {"
 6329        fn a() {
 6330            b();
 6331            if c() {
 6332                d(
 6333                    e
 6334                );
 6335        ˇ
 6336            }
 6337        }
 6338    "});
 6339
 6340    cx.set_state(indoc! {"
 6341        fn a() {
 6342            b();
 6343            ˇ
 6344        }
 6345    "});
 6346
 6347    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6348    cx.assert_editor_state(indoc! {"
 6349        fn a() {
 6350            b();
 6351            d(
 6352                e
 6353            );
 6354        ˇ
 6355        }
 6356    "});
 6357}
 6358
 6359#[gpui::test]
 6360fn test_select_all(cx: &mut TestAppContext) {
 6361    init_test(cx, |_| {});
 6362
 6363    let editor = cx.add_window(|window, cx| {
 6364        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6365        build_editor(buffer, window, cx)
 6366    });
 6367    _ = editor.update(cx, |editor, window, cx| {
 6368        editor.select_all(&SelectAll, window, cx);
 6369        assert_eq!(
 6370            editor.selections.display_ranges(cx),
 6371            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6372        );
 6373    });
 6374}
 6375
 6376#[gpui::test]
 6377fn test_select_line(cx: &mut TestAppContext) {
 6378    init_test(cx, |_| {});
 6379
 6380    let editor = cx.add_window(|window, cx| {
 6381        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6382        build_editor(buffer, window, cx)
 6383    });
 6384    _ = editor.update(cx, |editor, window, cx| {
 6385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6386            s.select_display_ranges([
 6387                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6388                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6389                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6390                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6391            ])
 6392        });
 6393        editor.select_line(&SelectLine, window, cx);
 6394        assert_eq!(
 6395            editor.selections.display_ranges(cx),
 6396            vec![
 6397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6398                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6399            ]
 6400        );
 6401    });
 6402
 6403    _ = editor.update(cx, |editor, window, cx| {
 6404        editor.select_line(&SelectLine, window, cx);
 6405        assert_eq!(
 6406            editor.selections.display_ranges(cx),
 6407            vec![
 6408                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6409                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6410            ]
 6411        );
 6412    });
 6413
 6414    _ = editor.update(cx, |editor, window, cx| {
 6415        editor.select_line(&SelectLine, window, cx);
 6416        assert_eq!(
 6417            editor.selections.display_ranges(cx),
 6418            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6419        );
 6420    });
 6421}
 6422
 6423#[gpui::test]
 6424async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6425    init_test(cx, |_| {});
 6426    let mut cx = EditorTestContext::new(cx).await;
 6427
 6428    #[track_caller]
 6429    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6430        cx.set_state(initial_state);
 6431        cx.update_editor(|e, window, cx| {
 6432            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6433        });
 6434        cx.assert_editor_state(expected_state);
 6435    }
 6436
 6437    // Selection starts and ends at the middle of lines, left-to-right
 6438    test(
 6439        &mut cx,
 6440        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6441        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6442    );
 6443    // Same thing, right-to-left
 6444    test(
 6445        &mut cx,
 6446        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6447        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6448    );
 6449
 6450    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6451    test(
 6452        &mut cx,
 6453        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6454        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6455    );
 6456    // Same thing, right-to-left
 6457    test(
 6458        &mut cx,
 6459        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6460        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6461    );
 6462
 6463    // Whole buffer, left-to-right, last line ends with newline
 6464    test(
 6465        &mut cx,
 6466        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6467        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6468    );
 6469    // Same thing, right-to-left
 6470    test(
 6471        &mut cx,
 6472        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6473        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6474    );
 6475
 6476    // Starts at the end of a line, ends at the start of another
 6477    test(
 6478        &mut cx,
 6479        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6480        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6481    );
 6482}
 6483
 6484#[gpui::test]
 6485async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6486    init_test(cx, |_| {});
 6487
 6488    let editor = cx.add_window(|window, cx| {
 6489        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6490        build_editor(buffer, window, cx)
 6491    });
 6492
 6493    // setup
 6494    _ = editor.update(cx, |editor, window, cx| {
 6495        editor.fold_creases(
 6496            vec![
 6497                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6498                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6499                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6500            ],
 6501            true,
 6502            window,
 6503            cx,
 6504        );
 6505        assert_eq!(
 6506            editor.display_text(cx),
 6507            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6508        );
 6509    });
 6510
 6511    _ = editor.update(cx, |editor, window, cx| {
 6512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6513            s.select_display_ranges([
 6514                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6515                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6516                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6517                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6518            ])
 6519        });
 6520        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6521        assert_eq!(
 6522            editor.display_text(cx),
 6523            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6524        );
 6525    });
 6526    EditorTestContext::for_editor(editor, cx)
 6527        .await
 6528        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6529
 6530    _ = editor.update(cx, |editor, window, cx| {
 6531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6532            s.select_display_ranges([
 6533                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6534            ])
 6535        });
 6536        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6537        assert_eq!(
 6538            editor.display_text(cx),
 6539            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6540        );
 6541        assert_eq!(
 6542            editor.selections.display_ranges(cx),
 6543            [
 6544                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6545                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6546                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6547                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6548                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6549                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6550                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6551            ]
 6552        );
 6553    });
 6554    EditorTestContext::for_editor(editor, cx)
 6555        .await
 6556        .assert_editor_state(
 6557            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6558        );
 6559}
 6560
 6561#[gpui::test]
 6562async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6563    init_test(cx, |_| {});
 6564
 6565    let mut cx = EditorTestContext::new(cx).await;
 6566
 6567    cx.set_state(indoc!(
 6568        r#"abc
 6569           defˇghi
 6570
 6571           jk
 6572           nlmo
 6573           "#
 6574    ));
 6575
 6576    cx.update_editor(|editor, window, cx| {
 6577        editor.add_selection_above(&Default::default(), window, cx);
 6578    });
 6579
 6580    cx.assert_editor_state(indoc!(
 6581        r#"abcˇ
 6582           defˇghi
 6583
 6584           jk
 6585           nlmo
 6586           "#
 6587    ));
 6588
 6589    cx.update_editor(|editor, window, cx| {
 6590        editor.add_selection_above(&Default::default(), window, cx);
 6591    });
 6592
 6593    cx.assert_editor_state(indoc!(
 6594        r#"abcˇ
 6595            defˇghi
 6596
 6597            jk
 6598            nlmo
 6599            "#
 6600    ));
 6601
 6602    cx.update_editor(|editor, window, cx| {
 6603        editor.add_selection_below(&Default::default(), window, cx);
 6604    });
 6605
 6606    cx.assert_editor_state(indoc!(
 6607        r#"abc
 6608           defˇghi
 6609
 6610           jk
 6611           nlmo
 6612           "#
 6613    ));
 6614
 6615    cx.update_editor(|editor, window, cx| {
 6616        editor.undo_selection(&Default::default(), window, cx);
 6617    });
 6618
 6619    cx.assert_editor_state(indoc!(
 6620        r#"abcˇ
 6621           defˇghi
 6622
 6623           jk
 6624           nlmo
 6625           "#
 6626    ));
 6627
 6628    cx.update_editor(|editor, window, cx| {
 6629        editor.redo_selection(&Default::default(), window, cx);
 6630    });
 6631
 6632    cx.assert_editor_state(indoc!(
 6633        r#"abc
 6634           defˇghi
 6635
 6636           jk
 6637           nlmo
 6638           "#
 6639    ));
 6640
 6641    cx.update_editor(|editor, window, cx| {
 6642        editor.add_selection_below(&Default::default(), window, cx);
 6643    });
 6644
 6645    cx.assert_editor_state(indoc!(
 6646        r#"abc
 6647           defˇghi
 6648           ˇ
 6649           jk
 6650           nlmo
 6651           "#
 6652    ));
 6653
 6654    cx.update_editor(|editor, window, cx| {
 6655        editor.add_selection_below(&Default::default(), window, cx);
 6656    });
 6657
 6658    cx.assert_editor_state(indoc!(
 6659        r#"abc
 6660           defˇghi
 6661           ˇ
 6662           jkˇ
 6663           nlmo
 6664           "#
 6665    ));
 6666
 6667    cx.update_editor(|editor, window, cx| {
 6668        editor.add_selection_below(&Default::default(), window, cx);
 6669    });
 6670
 6671    cx.assert_editor_state(indoc!(
 6672        r#"abc
 6673           defˇghi
 6674           ˇ
 6675           jkˇ
 6676           nlmˇo
 6677           "#
 6678    ));
 6679
 6680    cx.update_editor(|editor, window, cx| {
 6681        editor.add_selection_below(&Default::default(), window, cx);
 6682    });
 6683
 6684    cx.assert_editor_state(indoc!(
 6685        r#"abc
 6686           defˇghi
 6687           ˇ
 6688           jkˇ
 6689           nlmˇo
 6690           ˇ"#
 6691    ));
 6692
 6693    // change selections
 6694    cx.set_state(indoc!(
 6695        r#"abc
 6696           def«ˇg»hi
 6697
 6698           jk
 6699           nlmo
 6700           "#
 6701    ));
 6702
 6703    cx.update_editor(|editor, window, cx| {
 6704        editor.add_selection_below(&Default::default(), window, cx);
 6705    });
 6706
 6707    cx.assert_editor_state(indoc!(
 6708        r#"abc
 6709           def«ˇg»hi
 6710
 6711           jk
 6712           nlm«ˇo»
 6713           "#
 6714    ));
 6715
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_below(&Default::default(), window, cx);
 6718    });
 6719
 6720    cx.assert_editor_state(indoc!(
 6721        r#"abc
 6722           def«ˇg»hi
 6723
 6724           jk
 6725           nlm«ˇo»
 6726           "#
 6727    ));
 6728
 6729    cx.update_editor(|editor, window, cx| {
 6730        editor.add_selection_above(&Default::default(), window, cx);
 6731    });
 6732
 6733    cx.assert_editor_state(indoc!(
 6734        r#"abc
 6735           def«ˇg»hi
 6736
 6737           jk
 6738           nlmo
 6739           "#
 6740    ));
 6741
 6742    cx.update_editor(|editor, window, cx| {
 6743        editor.add_selection_above(&Default::default(), window, cx);
 6744    });
 6745
 6746    cx.assert_editor_state(indoc!(
 6747        r#"abc
 6748           def«ˇg»hi
 6749
 6750           jk
 6751           nlmo
 6752           "#
 6753    ));
 6754
 6755    // Change selections again
 6756    cx.set_state(indoc!(
 6757        r#"a«bc
 6758           defgˇ»hi
 6759
 6760           jk
 6761           nlmo
 6762           "#
 6763    ));
 6764
 6765    cx.update_editor(|editor, window, cx| {
 6766        editor.add_selection_below(&Default::default(), window, cx);
 6767    });
 6768
 6769    cx.assert_editor_state(indoc!(
 6770        r#"a«bcˇ»
 6771           d«efgˇ»hi
 6772
 6773           j«kˇ»
 6774           nlmo
 6775           "#
 6776    ));
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780    });
 6781    cx.assert_editor_state(indoc!(
 6782        r#"a«bcˇ»
 6783           d«efgˇ»hi
 6784
 6785           j«kˇ»
 6786           n«lmoˇ»
 6787           "#
 6788    ));
 6789    cx.update_editor(|editor, window, cx| {
 6790        editor.add_selection_above(&Default::default(), window, cx);
 6791    });
 6792
 6793    cx.assert_editor_state(indoc!(
 6794        r#"a«bcˇ»
 6795           d«efgˇ»hi
 6796
 6797           j«kˇ»
 6798           nlmo
 6799           "#
 6800    ));
 6801
 6802    // Change selections again
 6803    cx.set_state(indoc!(
 6804        r#"abc
 6805           d«ˇefghi
 6806
 6807           jk
 6808           nlm»o
 6809           "#
 6810    ));
 6811
 6812    cx.update_editor(|editor, window, cx| {
 6813        editor.add_selection_above(&Default::default(), window, cx);
 6814    });
 6815
 6816    cx.assert_editor_state(indoc!(
 6817        r#"a«ˇbc»
 6818           d«ˇef»ghi
 6819
 6820           j«ˇk»
 6821           n«ˇlm»o
 6822           "#
 6823    ));
 6824
 6825    cx.update_editor(|editor, window, cx| {
 6826        editor.add_selection_below(&Default::default(), window, cx);
 6827    });
 6828
 6829    cx.assert_editor_state(indoc!(
 6830        r#"abc
 6831           d«ˇef»ghi
 6832
 6833           j«ˇk»
 6834           n«ˇlm»o
 6835           "#
 6836    ));
 6837}
 6838
 6839#[gpui::test]
 6840async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6841    init_test(cx, |_| {});
 6842    let mut cx = EditorTestContext::new(cx).await;
 6843
 6844    cx.set_state(indoc!(
 6845        r#"line onˇe
 6846           liˇne two
 6847           line three
 6848           line four"#
 6849    ));
 6850
 6851    cx.update_editor(|editor, window, cx| {
 6852        editor.add_selection_below(&Default::default(), window, cx);
 6853    });
 6854
 6855    // test multiple cursors expand in the same direction
 6856    cx.assert_editor_state(indoc!(
 6857        r#"line onˇe
 6858           liˇne twˇo
 6859           liˇne three
 6860           line four"#
 6861    ));
 6862
 6863    cx.update_editor(|editor, window, cx| {
 6864        editor.add_selection_below(&Default::default(), window, cx);
 6865    });
 6866
 6867    cx.update_editor(|editor, window, cx| {
 6868        editor.add_selection_below(&Default::default(), window, cx);
 6869    });
 6870
 6871    // test multiple cursors expand below overflow
 6872    cx.assert_editor_state(indoc!(
 6873        r#"line onˇe
 6874           liˇne twˇo
 6875           liˇne thˇree
 6876           liˇne foˇur"#
 6877    ));
 6878
 6879    cx.update_editor(|editor, window, cx| {
 6880        editor.add_selection_above(&Default::default(), window, cx);
 6881    });
 6882
 6883    // test multiple cursors retrieves back correctly
 6884    cx.assert_editor_state(indoc!(
 6885        r#"line onˇe
 6886           liˇne twˇo
 6887           liˇne thˇree
 6888           line four"#
 6889    ));
 6890
 6891    cx.update_editor(|editor, window, cx| {
 6892        editor.add_selection_above(&Default::default(), window, cx);
 6893    });
 6894
 6895    cx.update_editor(|editor, window, cx| {
 6896        editor.add_selection_above(&Default::default(), window, cx);
 6897    });
 6898
 6899    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6900    cx.assert_editor_state(indoc!(
 6901        r#"liˇne onˇe
 6902           liˇne two
 6903           line three
 6904           line four"#
 6905    ));
 6906
 6907    cx.update_editor(|editor, window, cx| {
 6908        editor.undo_selection(&Default::default(), window, cx);
 6909    });
 6910
 6911    // test undo
 6912    cx.assert_editor_state(indoc!(
 6913        r#"line onˇe
 6914           liˇne twˇo
 6915           line three
 6916           line four"#
 6917    ));
 6918
 6919    cx.update_editor(|editor, window, cx| {
 6920        editor.redo_selection(&Default::default(), window, cx);
 6921    });
 6922
 6923    // test redo
 6924    cx.assert_editor_state(indoc!(
 6925        r#"liˇne onˇe
 6926           liˇne two
 6927           line three
 6928           line four"#
 6929    ));
 6930
 6931    cx.set_state(indoc!(
 6932        r#"abcd
 6933           ef«ghˇ»
 6934           ijkl
 6935           «mˇ»nop"#
 6936    ));
 6937
 6938    cx.update_editor(|editor, window, cx| {
 6939        editor.add_selection_above(&Default::default(), window, cx);
 6940    });
 6941
 6942    // test multiple selections expand in the same direction
 6943    cx.assert_editor_state(indoc!(
 6944        r#"ab«cdˇ»
 6945           ef«ghˇ»
 6946           «iˇ»jkl
 6947           «mˇ»nop"#
 6948    ));
 6949
 6950    cx.update_editor(|editor, window, cx| {
 6951        editor.add_selection_above(&Default::default(), window, cx);
 6952    });
 6953
 6954    // test multiple selection upward overflow
 6955    cx.assert_editor_state(indoc!(
 6956        r#"ab«cdˇ»
 6957           «eˇ»f«ghˇ»
 6958           «iˇ»jkl
 6959           «mˇ»nop"#
 6960    ));
 6961
 6962    cx.update_editor(|editor, window, cx| {
 6963        editor.add_selection_below(&Default::default(), window, cx);
 6964    });
 6965
 6966    // test multiple selection retrieves back correctly
 6967    cx.assert_editor_state(indoc!(
 6968        r#"abcd
 6969           ef«ghˇ»
 6970           «iˇ»jkl
 6971           «mˇ»nop"#
 6972    ));
 6973
 6974    cx.update_editor(|editor, window, cx| {
 6975        editor.add_selection_below(&Default::default(), window, cx);
 6976    });
 6977
 6978    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6979    cx.assert_editor_state(indoc!(
 6980        r#"abcd
 6981           ef«ghˇ»
 6982           ij«klˇ»
 6983           «mˇ»nop"#
 6984    ));
 6985
 6986    cx.update_editor(|editor, window, cx| {
 6987        editor.undo_selection(&Default::default(), window, cx);
 6988    });
 6989
 6990    // test undo
 6991    cx.assert_editor_state(indoc!(
 6992        r#"abcd
 6993           ef«ghˇ»
 6994           «iˇ»jkl
 6995           «mˇ»nop"#
 6996    ));
 6997
 6998    cx.update_editor(|editor, window, cx| {
 6999        editor.redo_selection(&Default::default(), window, cx);
 7000    });
 7001
 7002    // test redo
 7003    cx.assert_editor_state(indoc!(
 7004        r#"abcd
 7005           ef«ghˇ»
 7006           ij«klˇ»
 7007           «mˇ»nop"#
 7008    ));
 7009}
 7010
 7011#[gpui::test]
 7012async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7013    init_test(cx, |_| {});
 7014    let mut cx = EditorTestContext::new(cx).await;
 7015
 7016    cx.set_state(indoc!(
 7017        r#"line onˇe
 7018           liˇne two
 7019           line three
 7020           line four"#
 7021    ));
 7022
 7023    cx.update_editor(|editor, window, cx| {
 7024        editor.add_selection_below(&Default::default(), window, cx);
 7025        editor.add_selection_below(&Default::default(), window, cx);
 7026        editor.add_selection_below(&Default::default(), window, cx);
 7027    });
 7028
 7029    // initial state with two multi cursor groups
 7030    cx.assert_editor_state(indoc!(
 7031        r#"line onˇe
 7032           liˇne twˇo
 7033           liˇne thˇree
 7034           liˇne foˇur"#
 7035    ));
 7036
 7037    // add single cursor in middle - simulate opt click
 7038    cx.update_editor(|editor, window, cx| {
 7039        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7040        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7041        editor.end_selection(window, cx);
 7042    });
 7043
 7044    cx.assert_editor_state(indoc!(
 7045        r#"line onˇe
 7046           liˇne twˇo
 7047           liˇneˇ thˇree
 7048           liˇne foˇur"#
 7049    ));
 7050
 7051    cx.update_editor(|editor, window, cx| {
 7052        editor.add_selection_above(&Default::default(), window, cx);
 7053    });
 7054
 7055    // test new added selection expands above and existing selection shrinks
 7056    cx.assert_editor_state(indoc!(
 7057        r#"line onˇe
 7058           liˇneˇ twˇo
 7059           liˇneˇ thˇree
 7060           line four"#
 7061    ));
 7062
 7063    cx.update_editor(|editor, window, cx| {
 7064        editor.add_selection_above(&Default::default(), window, cx);
 7065    });
 7066
 7067    // test new added selection expands above and existing selection shrinks
 7068    cx.assert_editor_state(indoc!(
 7069        r#"lineˇ onˇe
 7070           liˇneˇ twˇo
 7071           lineˇ three
 7072           line four"#
 7073    ));
 7074
 7075    // intial state with two selection groups
 7076    cx.set_state(indoc!(
 7077        r#"abcd
 7078           ef«ghˇ»
 7079           ijkl
 7080           «mˇ»nop"#
 7081    ));
 7082
 7083    cx.update_editor(|editor, window, cx| {
 7084        editor.add_selection_above(&Default::default(), window, cx);
 7085        editor.add_selection_above(&Default::default(), window, cx);
 7086    });
 7087
 7088    cx.assert_editor_state(indoc!(
 7089        r#"ab«cdˇ»
 7090           «eˇ»f«ghˇ»
 7091           «iˇ»jkl
 7092           «mˇ»nop"#
 7093    ));
 7094
 7095    // add single selection in middle - simulate opt drag
 7096    cx.update_editor(|editor, window, cx| {
 7097        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7098        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7099        editor.update_selection(
 7100            DisplayPoint::new(DisplayRow(2), 4),
 7101            0,
 7102            gpui::Point::<f32>::default(),
 7103            window,
 7104            cx,
 7105        );
 7106        editor.end_selection(window, cx);
 7107    });
 7108
 7109    cx.assert_editor_state(indoc!(
 7110        r#"ab«cdˇ»
 7111           «eˇ»f«ghˇ»
 7112           «iˇ»jk«lˇ»
 7113           «mˇ»nop"#
 7114    ));
 7115
 7116    cx.update_editor(|editor, window, cx| {
 7117        editor.add_selection_below(&Default::default(), window, cx);
 7118    });
 7119
 7120    // test new added selection expands below, others shrinks from above
 7121    cx.assert_editor_state(indoc!(
 7122        r#"abcd
 7123           ef«ghˇ»
 7124           «iˇ»jk«lˇ»
 7125           «mˇ»no«pˇ»"#
 7126    ));
 7127}
 7128
 7129#[gpui::test]
 7130async fn test_select_next(cx: &mut TestAppContext) {
 7131    init_test(cx, |_| {});
 7132
 7133    let mut cx = EditorTestContext::new(cx).await;
 7134    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7135
 7136    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7137        .unwrap();
 7138    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7139
 7140    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7141        .unwrap();
 7142    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7143
 7144    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7145    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7146
 7147    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7148    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7149
 7150    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7151        .unwrap();
 7152    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7153
 7154    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7155        .unwrap();
 7156    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7157
 7158    // Test selection direction should be preserved
 7159    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7160
 7161    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7162        .unwrap();
 7163    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7164}
 7165
 7166#[gpui::test]
 7167async fn test_select_all_matches(cx: &mut TestAppContext) {
 7168    init_test(cx, |_| {});
 7169
 7170    let mut cx = EditorTestContext::new(cx).await;
 7171
 7172    // Test caret-only selections
 7173    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7174    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7175        .unwrap();
 7176    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7177
 7178    // Test left-to-right selections
 7179    cx.set_state("abc\n«abcˇ»\nabc");
 7180    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7181        .unwrap();
 7182    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7183
 7184    // Test right-to-left selections
 7185    cx.set_state("abc\n«ˇabc»\nabc");
 7186    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7187        .unwrap();
 7188    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7189
 7190    // Test selecting whitespace with caret selection
 7191    cx.set_state("abc\nˇ   abc\nabc");
 7192    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7193        .unwrap();
 7194    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7195
 7196    // Test selecting whitespace with left-to-right selection
 7197    cx.set_state("abc\n«ˇ  »abc\nabc");
 7198    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7199        .unwrap();
 7200    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7201
 7202    // Test no matches with right-to-left selection
 7203    cx.set_state("abc\n«  ˇ»abc\nabc");
 7204    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7205        .unwrap();
 7206    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7207
 7208    // Test with a single word and clip_at_line_ends=true (#29823)
 7209    cx.set_state("aˇbc");
 7210    cx.update_editor(|e, window, cx| {
 7211        e.set_clip_at_line_ends(true, cx);
 7212        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7213        e.set_clip_at_line_ends(false, cx);
 7214    });
 7215    cx.assert_editor_state("«abcˇ»");
 7216}
 7217
 7218#[gpui::test]
 7219async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7220    init_test(cx, |_| {});
 7221
 7222    let mut cx = EditorTestContext::new(cx).await;
 7223
 7224    let large_body_1 = "\nd".repeat(200);
 7225    let large_body_2 = "\ne".repeat(200);
 7226
 7227    cx.set_state(&format!(
 7228        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7229    ));
 7230    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7231        let scroll_position = editor.scroll_position(cx);
 7232        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7233        scroll_position
 7234    });
 7235
 7236    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7237        .unwrap();
 7238    cx.assert_editor_state(&format!(
 7239        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7240    ));
 7241    let scroll_position_after_selection =
 7242        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7243    assert_eq!(
 7244        initial_scroll_position, scroll_position_after_selection,
 7245        "Scroll position should not change after selecting all matches"
 7246    );
 7247}
 7248
 7249#[gpui::test]
 7250async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7251    init_test(cx, |_| {});
 7252
 7253    let mut cx = EditorLspTestContext::new_rust(
 7254        lsp::ServerCapabilities {
 7255            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7256            ..Default::default()
 7257        },
 7258        cx,
 7259    )
 7260    .await;
 7261
 7262    cx.set_state(indoc! {"
 7263        line 1
 7264        line 2
 7265        linˇe 3
 7266        line 4
 7267        line 5
 7268    "});
 7269
 7270    // Make an edit
 7271    cx.update_editor(|editor, window, cx| {
 7272        editor.handle_input("X", window, cx);
 7273    });
 7274
 7275    // Move cursor to a different position
 7276    cx.update_editor(|editor, window, cx| {
 7277        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7278            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7279        });
 7280    });
 7281
 7282    cx.assert_editor_state(indoc! {"
 7283        line 1
 7284        line 2
 7285        linXe 3
 7286        line 4
 7287        liˇne 5
 7288    "});
 7289
 7290    cx.lsp
 7291        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7292            Ok(Some(vec![lsp::TextEdit::new(
 7293                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7294                "PREFIX ".to_string(),
 7295            )]))
 7296        });
 7297
 7298    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7299        .unwrap()
 7300        .await
 7301        .unwrap();
 7302
 7303    cx.assert_editor_state(indoc! {"
 7304        PREFIX line 1
 7305        line 2
 7306        linXe 3
 7307        line 4
 7308        liˇne 5
 7309    "});
 7310
 7311    // Undo formatting
 7312    cx.update_editor(|editor, window, cx| {
 7313        editor.undo(&Default::default(), window, cx);
 7314    });
 7315
 7316    // Verify cursor moved back to position after edit
 7317    cx.assert_editor_state(indoc! {"
 7318        line 1
 7319        line 2
 7320        linXˇe 3
 7321        line 4
 7322        line 5
 7323    "});
 7324}
 7325
 7326#[gpui::test]
 7327async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7328    init_test(cx, |_| {});
 7329
 7330    let mut cx = EditorTestContext::new(cx).await;
 7331
 7332    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7333    cx.update_editor(|editor, window, cx| {
 7334        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7335    });
 7336
 7337    cx.set_state(indoc! {"
 7338        line 1
 7339        line 2
 7340        linˇe 3
 7341        line 4
 7342        line 5
 7343        line 6
 7344        line 7
 7345        line 8
 7346        line 9
 7347        line 10
 7348    "});
 7349
 7350    let snapshot = cx.buffer_snapshot();
 7351    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7352
 7353    cx.update(|_, cx| {
 7354        provider.update(cx, |provider, _| {
 7355            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7356                id: None,
 7357                edits: vec![(edit_position..edit_position, "X".into())],
 7358                edit_preview: None,
 7359            }))
 7360        })
 7361    });
 7362
 7363    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7364    cx.update_editor(|editor, window, cx| {
 7365        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7366    });
 7367
 7368    cx.assert_editor_state(indoc! {"
 7369        line 1
 7370        line 2
 7371        lineXˇ 3
 7372        line 4
 7373        line 5
 7374        line 6
 7375        line 7
 7376        line 8
 7377        line 9
 7378        line 10
 7379    "});
 7380
 7381    cx.update_editor(|editor, window, cx| {
 7382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7383            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7384        });
 7385    });
 7386
 7387    cx.assert_editor_state(indoc! {"
 7388        line 1
 7389        line 2
 7390        lineX 3
 7391        line 4
 7392        line 5
 7393        line 6
 7394        line 7
 7395        line 8
 7396        line 9
 7397        liˇne 10
 7398    "});
 7399
 7400    cx.update_editor(|editor, window, cx| {
 7401        editor.undo(&Default::default(), window, cx);
 7402    });
 7403
 7404    cx.assert_editor_state(indoc! {"
 7405        line 1
 7406        line 2
 7407        lineˇ 3
 7408        line 4
 7409        line 5
 7410        line 6
 7411        line 7
 7412        line 8
 7413        line 9
 7414        line 10
 7415    "});
 7416}
 7417
 7418#[gpui::test]
 7419async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7420    init_test(cx, |_| {});
 7421
 7422    let mut cx = EditorTestContext::new(cx).await;
 7423    cx.set_state(
 7424        r#"let foo = 2;
 7425lˇet foo = 2;
 7426let fooˇ = 2;
 7427let foo = 2;
 7428let foo = ˇ2;"#,
 7429    );
 7430
 7431    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7432        .unwrap();
 7433    cx.assert_editor_state(
 7434        r#"let foo = 2;
 7435«letˇ» foo = 2;
 7436let «fooˇ» = 2;
 7437let foo = 2;
 7438let foo = «2ˇ»;"#,
 7439    );
 7440
 7441    // noop for multiple selections with different contents
 7442    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7443        .unwrap();
 7444    cx.assert_editor_state(
 7445        r#"let foo = 2;
 7446«letˇ» foo = 2;
 7447let «fooˇ» = 2;
 7448let foo = 2;
 7449let foo = «2ˇ»;"#,
 7450    );
 7451
 7452    // Test last selection direction should be preserved
 7453    cx.set_state(
 7454        r#"let foo = 2;
 7455let foo = 2;
 7456let «fooˇ» = 2;
 7457let «ˇfoo» = 2;
 7458let foo = 2;"#,
 7459    );
 7460
 7461    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7462        .unwrap();
 7463    cx.assert_editor_state(
 7464        r#"let foo = 2;
 7465let foo = 2;
 7466let «fooˇ» = 2;
 7467let «ˇfoo» = 2;
 7468let «ˇfoo» = 2;"#,
 7469    );
 7470}
 7471
 7472#[gpui::test]
 7473async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7474    init_test(cx, |_| {});
 7475
 7476    let mut cx =
 7477        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7478
 7479    cx.assert_editor_state(indoc! {"
 7480        ˇbbb
 7481        ccc
 7482
 7483        bbb
 7484        ccc
 7485        "});
 7486    cx.dispatch_action(SelectPrevious::default());
 7487    cx.assert_editor_state(indoc! {"
 7488                «bbbˇ»
 7489                ccc
 7490
 7491                bbb
 7492                ccc
 7493                "});
 7494    cx.dispatch_action(SelectPrevious::default());
 7495    cx.assert_editor_state(indoc! {"
 7496                «bbbˇ»
 7497                ccc
 7498
 7499                «bbbˇ»
 7500                ccc
 7501                "});
 7502}
 7503
 7504#[gpui::test]
 7505async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7506    init_test(cx, |_| {});
 7507
 7508    let mut cx = EditorTestContext::new(cx).await;
 7509    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7510
 7511    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7512        .unwrap();
 7513    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7514
 7515    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7516        .unwrap();
 7517    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7518
 7519    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7520    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7521
 7522    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7523    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7524
 7525    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7526        .unwrap();
 7527    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7528
 7529    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7530        .unwrap();
 7531    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7532}
 7533
 7534#[gpui::test]
 7535async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7536    init_test(cx, |_| {});
 7537
 7538    let mut cx = EditorTestContext::new(cx).await;
 7539    cx.set_state("");
 7540
 7541    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7542        .unwrap();
 7543    cx.assert_editor_state("«aˇ»");
 7544    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7545        .unwrap();
 7546    cx.assert_editor_state("«aˇ»");
 7547}
 7548
 7549#[gpui::test]
 7550async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7551    init_test(cx, |_| {});
 7552
 7553    let mut cx = EditorTestContext::new(cx).await;
 7554    cx.set_state(
 7555        r#"let foo = 2;
 7556lˇet foo = 2;
 7557let fooˇ = 2;
 7558let foo = 2;
 7559let foo = ˇ2;"#,
 7560    );
 7561
 7562    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7563        .unwrap();
 7564    cx.assert_editor_state(
 7565        r#"let foo = 2;
 7566«letˇ» foo = 2;
 7567let «fooˇ» = 2;
 7568let foo = 2;
 7569let foo = «2ˇ»;"#,
 7570    );
 7571
 7572    // noop for multiple selections with different contents
 7573    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7574        .unwrap();
 7575    cx.assert_editor_state(
 7576        r#"let foo = 2;
 7577«letˇ» foo = 2;
 7578let «fooˇ» = 2;
 7579let foo = 2;
 7580let foo = «2ˇ»;"#,
 7581    );
 7582}
 7583
 7584#[gpui::test]
 7585async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7586    init_test(cx, |_| {});
 7587
 7588    let mut cx = EditorTestContext::new(cx).await;
 7589    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7590
 7591    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7592        .unwrap();
 7593    // selection direction is preserved
 7594    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7595
 7596    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7597        .unwrap();
 7598    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7599
 7600    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7601    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7602
 7603    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7604    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7605
 7606    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7607        .unwrap();
 7608    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7609
 7610    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7611        .unwrap();
 7612    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7613}
 7614
 7615#[gpui::test]
 7616async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7617    init_test(cx, |_| {});
 7618
 7619    let language = Arc::new(Language::new(
 7620        LanguageConfig::default(),
 7621        Some(tree_sitter_rust::LANGUAGE.into()),
 7622    ));
 7623
 7624    let text = r#"
 7625        use mod1::mod2::{mod3, mod4};
 7626
 7627        fn fn_1(param1: bool, param2: &str) {
 7628            let var1 = "text";
 7629        }
 7630    "#
 7631    .unindent();
 7632
 7633    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7634    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7635    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7636
 7637    editor
 7638        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7639        .await;
 7640
 7641    editor.update_in(cx, |editor, window, cx| {
 7642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7643            s.select_display_ranges([
 7644                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7645                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7646                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7647            ]);
 7648        });
 7649        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7650    });
 7651    editor.update(cx, |editor, cx| {
 7652        assert_text_with_selections(
 7653            editor,
 7654            indoc! {r#"
 7655                use mod1::mod2::{mod3, «mod4ˇ»};
 7656
 7657                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7658                    let var1 = "«ˇtext»";
 7659                }
 7660            "#},
 7661            cx,
 7662        );
 7663    });
 7664
 7665    editor.update_in(cx, |editor, window, cx| {
 7666        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7667    });
 7668    editor.update(cx, |editor, cx| {
 7669        assert_text_with_selections(
 7670            editor,
 7671            indoc! {r#"
 7672                use mod1::mod2::«{mod3, mod4}ˇ»;
 7673
 7674                «ˇfn fn_1(param1: bool, param2: &str) {
 7675                    let var1 = "text";
 7676 7677            "#},
 7678            cx,
 7679        );
 7680    });
 7681
 7682    editor.update_in(cx, |editor, window, cx| {
 7683        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7684    });
 7685    assert_eq!(
 7686        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7687        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7688    );
 7689
 7690    // Trying to expand the selected syntax node one more time has no effect.
 7691    editor.update_in(cx, |editor, window, cx| {
 7692        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7693    });
 7694    assert_eq!(
 7695        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7696        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7697    );
 7698
 7699    editor.update_in(cx, |editor, window, cx| {
 7700        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7701    });
 7702    editor.update(cx, |editor, cx| {
 7703        assert_text_with_selections(
 7704            editor,
 7705            indoc! {r#"
 7706                use mod1::mod2::«{mod3, mod4}ˇ»;
 7707
 7708                «ˇfn fn_1(param1: bool, param2: &str) {
 7709                    let var1 = "text";
 7710 7711            "#},
 7712            cx,
 7713        );
 7714    });
 7715
 7716    editor.update_in(cx, |editor, window, cx| {
 7717        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7718    });
 7719    editor.update(cx, |editor, cx| {
 7720        assert_text_with_selections(
 7721            editor,
 7722            indoc! {r#"
 7723                use mod1::mod2::{mod3, «mod4ˇ»};
 7724
 7725                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7726                    let var1 = "«ˇtext»";
 7727                }
 7728            "#},
 7729            cx,
 7730        );
 7731    });
 7732
 7733    editor.update_in(cx, |editor, window, cx| {
 7734        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7735    });
 7736    editor.update(cx, |editor, cx| {
 7737        assert_text_with_selections(
 7738            editor,
 7739            indoc! {r#"
 7740                use mod1::mod2::{mod3, mo«ˇ»d4};
 7741
 7742                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7743                    let var1 = "te«ˇ»xt";
 7744                }
 7745            "#},
 7746            cx,
 7747        );
 7748    });
 7749
 7750    // Trying to shrink the selected syntax node one more time has no effect.
 7751    editor.update_in(cx, |editor, window, cx| {
 7752        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7753    });
 7754    editor.update_in(cx, |editor, _, cx| {
 7755        assert_text_with_selections(
 7756            editor,
 7757            indoc! {r#"
 7758                use mod1::mod2::{mod3, mo«ˇ»d4};
 7759
 7760                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7761                    let var1 = "te«ˇ»xt";
 7762                }
 7763            "#},
 7764            cx,
 7765        );
 7766    });
 7767
 7768    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7769    // a fold.
 7770    editor.update_in(cx, |editor, window, cx| {
 7771        editor.fold_creases(
 7772            vec![
 7773                Crease::simple(
 7774                    Point::new(0, 21)..Point::new(0, 24),
 7775                    FoldPlaceholder::test(),
 7776                ),
 7777                Crease::simple(
 7778                    Point::new(3, 20)..Point::new(3, 22),
 7779                    FoldPlaceholder::test(),
 7780                ),
 7781            ],
 7782            true,
 7783            window,
 7784            cx,
 7785        );
 7786        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7787    });
 7788    editor.update(cx, |editor, cx| {
 7789        assert_text_with_selections(
 7790            editor,
 7791            indoc! {r#"
 7792                use mod1::mod2::«{mod3, mod4}ˇ»;
 7793
 7794                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7795                    let var1 = "«ˇtext»";
 7796                }
 7797            "#},
 7798            cx,
 7799        );
 7800    });
 7801}
 7802
 7803#[gpui::test]
 7804async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7805    init_test(cx, |_| {});
 7806
 7807    let language = Arc::new(Language::new(
 7808        LanguageConfig::default(),
 7809        Some(tree_sitter_rust::LANGUAGE.into()),
 7810    ));
 7811
 7812    let text = "let a = 2;";
 7813
 7814    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7815    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7816    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7817
 7818    editor
 7819        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7820        .await;
 7821
 7822    // Test case 1: Cursor at end of word
 7823    editor.update_in(cx, |editor, window, cx| {
 7824        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7825            s.select_display_ranges([
 7826                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7827            ]);
 7828        });
 7829    });
 7830    editor.update(cx, |editor, cx| {
 7831        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7832    });
 7833    editor.update_in(cx, |editor, window, cx| {
 7834        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7835    });
 7836    editor.update(cx, |editor, cx| {
 7837        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7838    });
 7839    editor.update_in(cx, |editor, window, cx| {
 7840        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7841    });
 7842    editor.update(cx, |editor, cx| {
 7843        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7844    });
 7845
 7846    // Test case 2: Cursor at end of statement
 7847    editor.update_in(cx, |editor, window, cx| {
 7848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7849            s.select_display_ranges([
 7850                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7851            ]);
 7852        });
 7853    });
 7854    editor.update(cx, |editor, cx| {
 7855        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7856    });
 7857    editor.update_in(cx, |editor, window, cx| {
 7858        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7859    });
 7860    editor.update(cx, |editor, cx| {
 7861        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7862    });
 7863}
 7864
 7865#[gpui::test]
 7866async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7867    init_test(cx, |_| {});
 7868
 7869    let language = Arc::new(Language::new(
 7870        LanguageConfig::default(),
 7871        Some(tree_sitter_rust::LANGUAGE.into()),
 7872    ));
 7873
 7874    let text = r#"
 7875        use mod1::mod2::{mod3, mod4};
 7876
 7877        fn fn_1(param1: bool, param2: &str) {
 7878            let var1 = "hello world";
 7879        }
 7880    "#
 7881    .unindent();
 7882
 7883    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7884    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7885    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7886
 7887    editor
 7888        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7889        .await;
 7890
 7891    // Test 1: Cursor on a letter of a string word
 7892    editor.update_in(cx, |editor, window, cx| {
 7893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7894            s.select_display_ranges([
 7895                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7896            ]);
 7897        });
 7898    });
 7899    editor.update_in(cx, |editor, window, cx| {
 7900        assert_text_with_selections(
 7901            editor,
 7902            indoc! {r#"
 7903                use mod1::mod2::{mod3, mod4};
 7904
 7905                fn fn_1(param1: bool, param2: &str) {
 7906                    let var1 = "hˇello world";
 7907                }
 7908            "#},
 7909            cx,
 7910        );
 7911        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7912        assert_text_with_selections(
 7913            editor,
 7914            indoc! {r#"
 7915                use mod1::mod2::{mod3, mod4};
 7916
 7917                fn fn_1(param1: bool, param2: &str) {
 7918                    let var1 = "«ˇhello» world";
 7919                }
 7920            "#},
 7921            cx,
 7922        );
 7923    });
 7924
 7925    // Test 2: Partial selection within a word
 7926    editor.update_in(cx, |editor, window, cx| {
 7927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7928            s.select_display_ranges([
 7929                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7930            ]);
 7931        });
 7932    });
 7933    editor.update_in(cx, |editor, window, cx| {
 7934        assert_text_with_selections(
 7935            editor,
 7936            indoc! {r#"
 7937                use mod1::mod2::{mod3, mod4};
 7938
 7939                fn fn_1(param1: bool, param2: &str) {
 7940                    let var1 = "h«elˇ»lo world";
 7941                }
 7942            "#},
 7943            cx,
 7944        );
 7945        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7946        assert_text_with_selections(
 7947            editor,
 7948            indoc! {r#"
 7949                use mod1::mod2::{mod3, mod4};
 7950
 7951                fn fn_1(param1: bool, param2: &str) {
 7952                    let var1 = "«ˇhello» world";
 7953                }
 7954            "#},
 7955            cx,
 7956        );
 7957    });
 7958
 7959    // Test 3: Complete word already selected
 7960    editor.update_in(cx, |editor, window, cx| {
 7961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7962            s.select_display_ranges([
 7963                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7964            ]);
 7965        });
 7966    });
 7967    editor.update_in(cx, |editor, window, cx| {
 7968        assert_text_with_selections(
 7969            editor,
 7970            indoc! {r#"
 7971                use mod1::mod2::{mod3, mod4};
 7972
 7973                fn fn_1(param1: bool, param2: &str) {
 7974                    let var1 = "«helloˇ» world";
 7975                }
 7976            "#},
 7977            cx,
 7978        );
 7979        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7980        assert_text_with_selections(
 7981            editor,
 7982            indoc! {r#"
 7983                use mod1::mod2::{mod3, mod4};
 7984
 7985                fn fn_1(param1: bool, param2: &str) {
 7986                    let var1 = "«hello worldˇ»";
 7987                }
 7988            "#},
 7989            cx,
 7990        );
 7991    });
 7992
 7993    // Test 4: Selection spanning across words
 7994    editor.update_in(cx, |editor, window, cx| {
 7995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7996            s.select_display_ranges([
 7997                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7998            ]);
 7999        });
 8000    });
 8001    editor.update_in(cx, |editor, window, cx| {
 8002        assert_text_with_selections(
 8003            editor,
 8004            indoc! {r#"
 8005                use mod1::mod2::{mod3, mod4};
 8006
 8007                fn fn_1(param1: bool, param2: &str) {
 8008                    let var1 = "hel«lo woˇ»rld";
 8009                }
 8010            "#},
 8011            cx,
 8012        );
 8013        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8014        assert_text_with_selections(
 8015            editor,
 8016            indoc! {r#"
 8017                use mod1::mod2::{mod3, mod4};
 8018
 8019                fn fn_1(param1: bool, param2: &str) {
 8020                    let var1 = "«ˇhello world»";
 8021                }
 8022            "#},
 8023            cx,
 8024        );
 8025    });
 8026
 8027    // Test 5: Expansion beyond string
 8028    editor.update_in(cx, |editor, window, cx| {
 8029        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8030        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8031        assert_text_with_selections(
 8032            editor,
 8033            indoc! {r#"
 8034                use mod1::mod2::{mod3, mod4};
 8035
 8036                fn fn_1(param1: bool, param2: &str) {
 8037                    «ˇlet var1 = "hello world";»
 8038                }
 8039            "#},
 8040            cx,
 8041        );
 8042    });
 8043}
 8044
 8045#[gpui::test]
 8046async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8047    init_test(cx, |_| {});
 8048
 8049    let base_text = r#"
 8050        impl A {
 8051            // this is an uncommitted comment
 8052
 8053            fn b() {
 8054                c();
 8055            }
 8056
 8057            // this is another uncommitted comment
 8058
 8059            fn d() {
 8060                // e
 8061                // f
 8062            }
 8063        }
 8064
 8065        fn g() {
 8066            // h
 8067        }
 8068    "#
 8069    .unindent();
 8070
 8071    let text = r#"
 8072        ˇimpl A {
 8073
 8074            fn b() {
 8075                c();
 8076            }
 8077
 8078            fn d() {
 8079                // e
 8080                // f
 8081            }
 8082        }
 8083
 8084        fn g() {
 8085            // h
 8086        }
 8087    "#
 8088    .unindent();
 8089
 8090    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8091    cx.set_state(&text);
 8092    cx.set_head_text(&base_text);
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8095    });
 8096
 8097    cx.assert_state_with_diff(
 8098        "
 8099        ˇimpl A {
 8100      -     // this is an uncommitted comment
 8101
 8102            fn b() {
 8103                c();
 8104            }
 8105
 8106      -     // this is another uncommitted comment
 8107      -
 8108            fn d() {
 8109                // e
 8110                // f
 8111            }
 8112        }
 8113
 8114        fn g() {
 8115            // h
 8116        }
 8117    "
 8118        .unindent(),
 8119    );
 8120
 8121    let expected_display_text = "
 8122        impl A {
 8123            // this is an uncommitted comment
 8124
 8125            fn b() {
 8126 8127            }
 8128
 8129            // this is another uncommitted comment
 8130
 8131            fn d() {
 8132 8133            }
 8134        }
 8135
 8136        fn g() {
 8137 8138        }
 8139        "
 8140    .unindent();
 8141
 8142    cx.update_editor(|editor, window, cx| {
 8143        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8144        assert_eq!(editor.display_text(cx), expected_display_text);
 8145    });
 8146}
 8147
 8148#[gpui::test]
 8149async fn test_autoindent(cx: &mut TestAppContext) {
 8150    init_test(cx, |_| {});
 8151
 8152    let language = Arc::new(
 8153        Language::new(
 8154            LanguageConfig {
 8155                brackets: BracketPairConfig {
 8156                    pairs: vec![
 8157                        BracketPair {
 8158                            start: "{".to_string(),
 8159                            end: "}".to_string(),
 8160                            close: false,
 8161                            surround: false,
 8162                            newline: true,
 8163                        },
 8164                        BracketPair {
 8165                            start: "(".to_string(),
 8166                            end: ")".to_string(),
 8167                            close: false,
 8168                            surround: false,
 8169                            newline: true,
 8170                        },
 8171                    ],
 8172                    ..Default::default()
 8173                },
 8174                ..Default::default()
 8175            },
 8176            Some(tree_sitter_rust::LANGUAGE.into()),
 8177        )
 8178        .with_indents_query(
 8179            r#"
 8180                (_ "(" ")" @end) @indent
 8181                (_ "{" "}" @end) @indent
 8182            "#,
 8183        )
 8184        .unwrap(),
 8185    );
 8186
 8187    let text = "fn a() {}";
 8188
 8189    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8190    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8191    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8192    editor
 8193        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8194        .await;
 8195
 8196    editor.update_in(cx, |editor, window, cx| {
 8197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8198            s.select_ranges([5..5, 8..8, 9..9])
 8199        });
 8200        editor.newline(&Newline, window, cx);
 8201        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8202        assert_eq!(
 8203            editor.selections.ranges(cx),
 8204            &[
 8205                Point::new(1, 4)..Point::new(1, 4),
 8206                Point::new(3, 4)..Point::new(3, 4),
 8207                Point::new(5, 0)..Point::new(5, 0)
 8208            ]
 8209        );
 8210    });
 8211}
 8212
 8213#[gpui::test]
 8214async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8215    init_test(cx, |_| {});
 8216
 8217    {
 8218        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8219        cx.set_state(indoc! {"
 8220            impl A {
 8221
 8222                fn b() {}
 8223
 8224            «fn c() {
 8225
 8226            }ˇ»
 8227            }
 8228        "});
 8229
 8230        cx.update_editor(|editor, window, cx| {
 8231            editor.autoindent(&Default::default(), window, cx);
 8232        });
 8233
 8234        cx.assert_editor_state(indoc! {"
 8235            impl A {
 8236
 8237                fn b() {}
 8238
 8239                «fn c() {
 8240
 8241                }ˇ»
 8242            }
 8243        "});
 8244    }
 8245
 8246    {
 8247        let mut cx = EditorTestContext::new_multibuffer(
 8248            cx,
 8249            [indoc! { "
 8250                impl A {
 8251                «
 8252                // a
 8253                fn b(){}
 8254                »
 8255                «
 8256                    }
 8257                    fn c(){}
 8258                »
 8259            "}],
 8260        );
 8261
 8262        let buffer = cx.update_editor(|editor, _, cx| {
 8263            let buffer = editor.buffer().update(cx, |buffer, _| {
 8264                buffer.all_buffers().iter().next().unwrap().clone()
 8265            });
 8266            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8267            buffer
 8268        });
 8269
 8270        cx.run_until_parked();
 8271        cx.update_editor(|editor, window, cx| {
 8272            editor.select_all(&Default::default(), window, cx);
 8273            editor.autoindent(&Default::default(), window, cx)
 8274        });
 8275        cx.run_until_parked();
 8276
 8277        cx.update(|_, cx| {
 8278            assert_eq!(
 8279                buffer.read(cx).text(),
 8280                indoc! { "
 8281                    impl A {
 8282
 8283                        // a
 8284                        fn b(){}
 8285
 8286
 8287                    }
 8288                    fn c(){}
 8289
 8290                " }
 8291            )
 8292        });
 8293    }
 8294}
 8295
 8296#[gpui::test]
 8297async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8298    init_test(cx, |_| {});
 8299
 8300    let mut cx = EditorTestContext::new(cx).await;
 8301
 8302    let language = Arc::new(Language::new(
 8303        LanguageConfig {
 8304            brackets: BracketPairConfig {
 8305                pairs: vec![
 8306                    BracketPair {
 8307                        start: "{".to_string(),
 8308                        end: "}".to_string(),
 8309                        close: true,
 8310                        surround: true,
 8311                        newline: true,
 8312                    },
 8313                    BracketPair {
 8314                        start: "(".to_string(),
 8315                        end: ")".to_string(),
 8316                        close: true,
 8317                        surround: true,
 8318                        newline: true,
 8319                    },
 8320                    BracketPair {
 8321                        start: "/*".to_string(),
 8322                        end: " */".to_string(),
 8323                        close: true,
 8324                        surround: true,
 8325                        newline: true,
 8326                    },
 8327                    BracketPair {
 8328                        start: "[".to_string(),
 8329                        end: "]".to_string(),
 8330                        close: false,
 8331                        surround: false,
 8332                        newline: true,
 8333                    },
 8334                    BracketPair {
 8335                        start: "\"".to_string(),
 8336                        end: "\"".to_string(),
 8337                        close: true,
 8338                        surround: true,
 8339                        newline: false,
 8340                    },
 8341                    BracketPair {
 8342                        start: "<".to_string(),
 8343                        end: ">".to_string(),
 8344                        close: false,
 8345                        surround: true,
 8346                        newline: true,
 8347                    },
 8348                ],
 8349                ..Default::default()
 8350            },
 8351            autoclose_before: "})]".to_string(),
 8352            ..Default::default()
 8353        },
 8354        Some(tree_sitter_rust::LANGUAGE.into()),
 8355    ));
 8356
 8357    cx.language_registry().add(language.clone());
 8358    cx.update_buffer(|buffer, cx| {
 8359        buffer.set_language(Some(language), cx);
 8360    });
 8361
 8362    cx.set_state(
 8363        &r#"
 8364            🏀ˇ
 8365            εˇ
 8366            ❤️ˇ
 8367        "#
 8368        .unindent(),
 8369    );
 8370
 8371    // autoclose multiple nested brackets at multiple cursors
 8372    cx.update_editor(|editor, window, cx| {
 8373        editor.handle_input("{", window, cx);
 8374        editor.handle_input("{", window, cx);
 8375        editor.handle_input("{", window, cx);
 8376    });
 8377    cx.assert_editor_state(
 8378        &"
 8379            🏀{{{ˇ}}}
 8380            ε{{{ˇ}}}
 8381            ❤️{{{ˇ}}}
 8382        "
 8383        .unindent(),
 8384    );
 8385
 8386    // insert a different closing bracket
 8387    cx.update_editor(|editor, window, cx| {
 8388        editor.handle_input(")", window, cx);
 8389    });
 8390    cx.assert_editor_state(
 8391        &"
 8392            🏀{{{)ˇ}}}
 8393            ε{{{)ˇ}}}
 8394            ❤️{{{)ˇ}}}
 8395        "
 8396        .unindent(),
 8397    );
 8398
 8399    // skip over the auto-closed brackets when typing a closing bracket
 8400    cx.update_editor(|editor, window, cx| {
 8401        editor.move_right(&MoveRight, window, cx);
 8402        editor.handle_input("}", window, cx);
 8403        editor.handle_input("}", window, cx);
 8404        editor.handle_input("}", window, cx);
 8405    });
 8406    cx.assert_editor_state(
 8407        &"
 8408            🏀{{{)}}}}ˇ
 8409            ε{{{)}}}}ˇ
 8410            ❤️{{{)}}}}ˇ
 8411        "
 8412        .unindent(),
 8413    );
 8414
 8415    // autoclose multi-character pairs
 8416    cx.set_state(
 8417        &"
 8418            ˇ
 8419            ˇ
 8420        "
 8421        .unindent(),
 8422    );
 8423    cx.update_editor(|editor, window, cx| {
 8424        editor.handle_input("/", window, cx);
 8425        editor.handle_input("*", window, cx);
 8426    });
 8427    cx.assert_editor_state(
 8428        &"
 8429            /*ˇ */
 8430            /*ˇ */
 8431        "
 8432        .unindent(),
 8433    );
 8434
 8435    // one cursor autocloses a multi-character pair, one cursor
 8436    // does not autoclose.
 8437    cx.set_state(
 8438        &"
 8439 8440            ˇ
 8441        "
 8442        .unindent(),
 8443    );
 8444    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8445    cx.assert_editor_state(
 8446        &"
 8447            /*ˇ */
 8448 8449        "
 8450        .unindent(),
 8451    );
 8452
 8453    // Don't autoclose if the next character isn't whitespace and isn't
 8454    // listed in the language's "autoclose_before" section.
 8455    cx.set_state("ˇa b");
 8456    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8457    cx.assert_editor_state("{ˇa b");
 8458
 8459    // Don't autoclose if `close` is false for the bracket pair
 8460    cx.set_state("ˇ");
 8461    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8462    cx.assert_editor_state("");
 8463
 8464    // Surround with brackets if text is selected
 8465    cx.set_state("«aˇ» b");
 8466    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8467    cx.assert_editor_state("{«aˇ»} b");
 8468
 8469    // Autoclose when not immediately after a word character
 8470    cx.set_state("a ˇ");
 8471    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8472    cx.assert_editor_state("a \"ˇ\"");
 8473
 8474    // Autoclose pair where the start and end characters are the same
 8475    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8476    cx.assert_editor_state("a \"\"ˇ");
 8477
 8478    // Don't autoclose when immediately after a word character
 8479    cx.set_state("");
 8480    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8481    cx.assert_editor_state("a\"ˇ");
 8482
 8483    // Do autoclose when after a non-word character
 8484    cx.set_state("");
 8485    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8486    cx.assert_editor_state("{\"ˇ\"");
 8487
 8488    // Non identical pairs autoclose regardless of preceding character
 8489    cx.set_state("");
 8490    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8491    cx.assert_editor_state("a{ˇ}");
 8492
 8493    // Don't autoclose pair if autoclose is disabled
 8494    cx.set_state("ˇ");
 8495    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8496    cx.assert_editor_state("");
 8497
 8498    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8499    cx.set_state("«aˇ» b");
 8500    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8501    cx.assert_editor_state("<«aˇ»> b");
 8502}
 8503
 8504#[gpui::test]
 8505async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8506    init_test(cx, |settings| {
 8507        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8508    });
 8509
 8510    let mut cx = EditorTestContext::new(cx).await;
 8511
 8512    let language = Arc::new(Language::new(
 8513        LanguageConfig {
 8514            brackets: BracketPairConfig {
 8515                pairs: vec![
 8516                    BracketPair {
 8517                        start: "{".to_string(),
 8518                        end: "}".to_string(),
 8519                        close: true,
 8520                        surround: true,
 8521                        newline: true,
 8522                    },
 8523                    BracketPair {
 8524                        start: "(".to_string(),
 8525                        end: ")".to_string(),
 8526                        close: true,
 8527                        surround: true,
 8528                        newline: true,
 8529                    },
 8530                    BracketPair {
 8531                        start: "[".to_string(),
 8532                        end: "]".to_string(),
 8533                        close: false,
 8534                        surround: false,
 8535                        newline: true,
 8536                    },
 8537                ],
 8538                ..Default::default()
 8539            },
 8540            autoclose_before: "})]".to_string(),
 8541            ..Default::default()
 8542        },
 8543        Some(tree_sitter_rust::LANGUAGE.into()),
 8544    ));
 8545
 8546    cx.language_registry().add(language.clone());
 8547    cx.update_buffer(|buffer, cx| {
 8548        buffer.set_language(Some(language), cx);
 8549    });
 8550
 8551    cx.set_state(
 8552        &"
 8553            ˇ
 8554            ˇ
 8555            ˇ
 8556        "
 8557        .unindent(),
 8558    );
 8559
 8560    // ensure only matching closing brackets are skipped over
 8561    cx.update_editor(|editor, window, cx| {
 8562        editor.handle_input("}", window, cx);
 8563        editor.move_left(&MoveLeft, window, cx);
 8564        editor.handle_input(")", window, cx);
 8565        editor.move_left(&MoveLeft, window, cx);
 8566    });
 8567    cx.assert_editor_state(
 8568        &"
 8569            ˇ)}
 8570            ˇ)}
 8571            ˇ)}
 8572        "
 8573        .unindent(),
 8574    );
 8575
 8576    // skip-over closing brackets at multiple cursors
 8577    cx.update_editor(|editor, window, cx| {
 8578        editor.handle_input(")", window, cx);
 8579        editor.handle_input("}", window, cx);
 8580    });
 8581    cx.assert_editor_state(
 8582        &"
 8583            )}ˇ
 8584            )}ˇ
 8585            )}ˇ
 8586        "
 8587        .unindent(),
 8588    );
 8589
 8590    // ignore non-close brackets
 8591    cx.update_editor(|editor, window, cx| {
 8592        editor.handle_input("]", window, cx);
 8593        editor.move_left(&MoveLeft, window, cx);
 8594        editor.handle_input("]", window, cx);
 8595    });
 8596    cx.assert_editor_state(
 8597        &"
 8598            )}]ˇ]
 8599            )}]ˇ]
 8600            )}]ˇ]
 8601        "
 8602        .unindent(),
 8603    );
 8604}
 8605
 8606#[gpui::test]
 8607async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8608    init_test(cx, |_| {});
 8609
 8610    let mut cx = EditorTestContext::new(cx).await;
 8611
 8612    let html_language = Arc::new(
 8613        Language::new(
 8614            LanguageConfig {
 8615                name: "HTML".into(),
 8616                brackets: BracketPairConfig {
 8617                    pairs: vec![
 8618                        BracketPair {
 8619                            start: "<".into(),
 8620                            end: ">".into(),
 8621                            close: true,
 8622                            ..Default::default()
 8623                        },
 8624                        BracketPair {
 8625                            start: "{".into(),
 8626                            end: "}".into(),
 8627                            close: true,
 8628                            ..Default::default()
 8629                        },
 8630                        BracketPair {
 8631                            start: "(".into(),
 8632                            end: ")".into(),
 8633                            close: true,
 8634                            ..Default::default()
 8635                        },
 8636                    ],
 8637                    ..Default::default()
 8638                },
 8639                autoclose_before: "})]>".into(),
 8640                ..Default::default()
 8641            },
 8642            Some(tree_sitter_html::LANGUAGE.into()),
 8643        )
 8644        .with_injection_query(
 8645            r#"
 8646            (script_element
 8647                (raw_text) @injection.content
 8648                (#set! injection.language "javascript"))
 8649            "#,
 8650        )
 8651        .unwrap(),
 8652    );
 8653
 8654    let javascript_language = Arc::new(Language::new(
 8655        LanguageConfig {
 8656            name: "JavaScript".into(),
 8657            brackets: BracketPairConfig {
 8658                pairs: vec![
 8659                    BracketPair {
 8660                        start: "/*".into(),
 8661                        end: " */".into(),
 8662                        close: true,
 8663                        ..Default::default()
 8664                    },
 8665                    BracketPair {
 8666                        start: "{".into(),
 8667                        end: "}".into(),
 8668                        close: true,
 8669                        ..Default::default()
 8670                    },
 8671                    BracketPair {
 8672                        start: "(".into(),
 8673                        end: ")".into(),
 8674                        close: true,
 8675                        ..Default::default()
 8676                    },
 8677                ],
 8678                ..Default::default()
 8679            },
 8680            autoclose_before: "})]>".into(),
 8681            ..Default::default()
 8682        },
 8683        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8684    ));
 8685
 8686    cx.language_registry().add(html_language.clone());
 8687    cx.language_registry().add(javascript_language.clone());
 8688
 8689    cx.update_buffer(|buffer, cx| {
 8690        buffer.set_language(Some(html_language), cx);
 8691    });
 8692
 8693    cx.set_state(
 8694        &r#"
 8695            <body>ˇ
 8696                <script>
 8697                    var x = 1;ˇ
 8698                </script>
 8699            </body>ˇ
 8700        "#
 8701        .unindent(),
 8702    );
 8703
 8704    // Precondition: different languages are active at different locations.
 8705    cx.update_editor(|editor, window, cx| {
 8706        let snapshot = editor.snapshot(window, cx);
 8707        let cursors = editor.selections.ranges::<usize>(cx);
 8708        let languages = cursors
 8709            .iter()
 8710            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8711            .collect::<Vec<_>>();
 8712        assert_eq!(
 8713            languages,
 8714            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8715        );
 8716    });
 8717
 8718    // Angle brackets autoclose in HTML, but not JavaScript.
 8719    cx.update_editor(|editor, window, cx| {
 8720        editor.handle_input("<", window, cx);
 8721        editor.handle_input("a", window, cx);
 8722    });
 8723    cx.assert_editor_state(
 8724        &r#"
 8725            <body><aˇ>
 8726                <script>
 8727                    var x = 1;<aˇ
 8728                </script>
 8729            </body><aˇ>
 8730        "#
 8731        .unindent(),
 8732    );
 8733
 8734    // Curly braces and parens autoclose in both HTML and JavaScript.
 8735    cx.update_editor(|editor, window, cx| {
 8736        editor.handle_input(" b=", window, cx);
 8737        editor.handle_input("{", window, cx);
 8738        editor.handle_input("c", window, cx);
 8739        editor.handle_input("(", window, cx);
 8740    });
 8741    cx.assert_editor_state(
 8742        &r#"
 8743            <body><a b={c(ˇ)}>
 8744                <script>
 8745                    var x = 1;<a b={c(ˇ)}
 8746                </script>
 8747            </body><a b={c(ˇ)}>
 8748        "#
 8749        .unindent(),
 8750    );
 8751
 8752    // Brackets that were already autoclosed are skipped.
 8753    cx.update_editor(|editor, window, cx| {
 8754        editor.handle_input(")", window, cx);
 8755        editor.handle_input("d", window, cx);
 8756        editor.handle_input("}", window, cx);
 8757    });
 8758    cx.assert_editor_state(
 8759        &r#"
 8760            <body><a b={c()d}ˇ>
 8761                <script>
 8762                    var x = 1;<a b={c()d}ˇ
 8763                </script>
 8764            </body><a b={c()d}ˇ>
 8765        "#
 8766        .unindent(),
 8767    );
 8768    cx.update_editor(|editor, window, cx| {
 8769        editor.handle_input(">", window, cx);
 8770    });
 8771    cx.assert_editor_state(
 8772        &r#"
 8773            <body><a b={c()d}>ˇ
 8774                <script>
 8775                    var x = 1;<a b={c()d}>ˇ
 8776                </script>
 8777            </body><a b={c()d}>ˇ
 8778        "#
 8779        .unindent(),
 8780    );
 8781
 8782    // Reset
 8783    cx.set_state(
 8784        &r#"
 8785            <body>ˇ
 8786                <script>
 8787                    var x = 1;ˇ
 8788                </script>
 8789            </body>ˇ
 8790        "#
 8791        .unindent(),
 8792    );
 8793
 8794    cx.update_editor(|editor, window, cx| {
 8795        editor.handle_input("<", window, cx);
 8796    });
 8797    cx.assert_editor_state(
 8798        &r#"
 8799            <body><ˇ>
 8800                <script>
 8801                    var x = 1;<ˇ
 8802                </script>
 8803            </body><ˇ>
 8804        "#
 8805        .unindent(),
 8806    );
 8807
 8808    // When backspacing, the closing angle brackets are removed.
 8809    cx.update_editor(|editor, window, cx| {
 8810        editor.backspace(&Backspace, window, cx);
 8811    });
 8812    cx.assert_editor_state(
 8813        &r#"
 8814            <body>ˇ
 8815                <script>
 8816                    var x = 1;ˇ
 8817                </script>
 8818            </body>ˇ
 8819        "#
 8820        .unindent(),
 8821    );
 8822
 8823    // Block comments autoclose in JavaScript, but not HTML.
 8824    cx.update_editor(|editor, window, cx| {
 8825        editor.handle_input("/", window, cx);
 8826        editor.handle_input("*", window, cx);
 8827    });
 8828    cx.assert_editor_state(
 8829        &r#"
 8830            <body>/*ˇ
 8831                <script>
 8832                    var x = 1;/*ˇ */
 8833                </script>
 8834            </body>/*ˇ
 8835        "#
 8836        .unindent(),
 8837    );
 8838}
 8839
 8840#[gpui::test]
 8841async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8842    init_test(cx, |_| {});
 8843
 8844    let mut cx = EditorTestContext::new(cx).await;
 8845
 8846    let rust_language = Arc::new(
 8847        Language::new(
 8848            LanguageConfig {
 8849                name: "Rust".into(),
 8850                brackets: serde_json::from_value(json!([
 8851                    { "start": "{", "end": "}", "close": true, "newline": true },
 8852                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8853                ]))
 8854                .unwrap(),
 8855                autoclose_before: "})]>".into(),
 8856                ..Default::default()
 8857            },
 8858            Some(tree_sitter_rust::LANGUAGE.into()),
 8859        )
 8860        .with_override_query("(string_literal) @string")
 8861        .unwrap(),
 8862    );
 8863
 8864    cx.language_registry().add(rust_language.clone());
 8865    cx.update_buffer(|buffer, cx| {
 8866        buffer.set_language(Some(rust_language), cx);
 8867    });
 8868
 8869    cx.set_state(
 8870        &r#"
 8871            let x = ˇ
 8872        "#
 8873        .unindent(),
 8874    );
 8875
 8876    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8877    cx.update_editor(|editor, window, cx| {
 8878        editor.handle_input("\"", window, cx);
 8879    });
 8880    cx.assert_editor_state(
 8881        &r#"
 8882            let x = "ˇ"
 8883        "#
 8884        .unindent(),
 8885    );
 8886
 8887    // Inserting another quotation mark. The cursor moves across the existing
 8888    // automatically-inserted quotation mark.
 8889    cx.update_editor(|editor, window, cx| {
 8890        editor.handle_input("\"", window, cx);
 8891    });
 8892    cx.assert_editor_state(
 8893        &r#"
 8894            let x = ""ˇ
 8895        "#
 8896        .unindent(),
 8897    );
 8898
 8899    // Reset
 8900    cx.set_state(
 8901        &r#"
 8902            let x = ˇ
 8903        "#
 8904        .unindent(),
 8905    );
 8906
 8907    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8908    cx.update_editor(|editor, window, cx| {
 8909        editor.handle_input("\"", window, cx);
 8910        editor.handle_input(" ", window, cx);
 8911        editor.move_left(&Default::default(), window, cx);
 8912        editor.handle_input("\\", window, cx);
 8913        editor.handle_input("\"", window, cx);
 8914    });
 8915    cx.assert_editor_state(
 8916        &r#"
 8917            let x = "\"ˇ "
 8918        "#
 8919        .unindent(),
 8920    );
 8921
 8922    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8923    // mark. Nothing is inserted.
 8924    cx.update_editor(|editor, window, cx| {
 8925        editor.move_right(&Default::default(), window, cx);
 8926        editor.handle_input("\"", window, cx);
 8927    });
 8928    cx.assert_editor_state(
 8929        &r#"
 8930            let x = "\" "ˇ
 8931        "#
 8932        .unindent(),
 8933    );
 8934}
 8935
 8936#[gpui::test]
 8937async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8938    init_test(cx, |_| {});
 8939
 8940    let language = Arc::new(Language::new(
 8941        LanguageConfig {
 8942            brackets: BracketPairConfig {
 8943                pairs: vec![
 8944                    BracketPair {
 8945                        start: "{".to_string(),
 8946                        end: "}".to_string(),
 8947                        close: true,
 8948                        surround: true,
 8949                        newline: true,
 8950                    },
 8951                    BracketPair {
 8952                        start: "/* ".to_string(),
 8953                        end: "*/".to_string(),
 8954                        close: true,
 8955                        surround: true,
 8956                        ..Default::default()
 8957                    },
 8958                ],
 8959                ..Default::default()
 8960            },
 8961            ..Default::default()
 8962        },
 8963        Some(tree_sitter_rust::LANGUAGE.into()),
 8964    ));
 8965
 8966    let text = r#"
 8967        a
 8968        b
 8969        c
 8970    "#
 8971    .unindent();
 8972
 8973    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8975    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8976    editor
 8977        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8978        .await;
 8979
 8980    editor.update_in(cx, |editor, window, cx| {
 8981        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8982            s.select_display_ranges([
 8983                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8984                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8985                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8986            ])
 8987        });
 8988
 8989        editor.handle_input("{", window, cx);
 8990        editor.handle_input("{", window, cx);
 8991        editor.handle_input("{", window, cx);
 8992        assert_eq!(
 8993            editor.text(cx),
 8994            "
 8995                {{{a}}}
 8996                {{{b}}}
 8997                {{{c}}}
 8998            "
 8999            .unindent()
 9000        );
 9001        assert_eq!(
 9002            editor.selections.display_ranges(cx),
 9003            [
 9004                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9005                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9006                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9007            ]
 9008        );
 9009
 9010        editor.undo(&Undo, window, cx);
 9011        editor.undo(&Undo, window, cx);
 9012        editor.undo(&Undo, window, cx);
 9013        assert_eq!(
 9014            editor.text(cx),
 9015            "
 9016                a
 9017                b
 9018                c
 9019            "
 9020            .unindent()
 9021        );
 9022        assert_eq!(
 9023            editor.selections.display_ranges(cx),
 9024            [
 9025                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9026                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9027                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9028            ]
 9029        );
 9030
 9031        // Ensure inserting the first character of a multi-byte bracket pair
 9032        // doesn't surround the selections with the bracket.
 9033        editor.handle_input("/", window, cx);
 9034        assert_eq!(
 9035            editor.text(cx),
 9036            "
 9037                /
 9038                /
 9039                /
 9040            "
 9041            .unindent()
 9042        );
 9043        assert_eq!(
 9044            editor.selections.display_ranges(cx),
 9045            [
 9046                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9047                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9048                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9049            ]
 9050        );
 9051
 9052        editor.undo(&Undo, window, cx);
 9053        assert_eq!(
 9054            editor.text(cx),
 9055            "
 9056                a
 9057                b
 9058                c
 9059            "
 9060            .unindent()
 9061        );
 9062        assert_eq!(
 9063            editor.selections.display_ranges(cx),
 9064            [
 9065                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9066                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9067                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9068            ]
 9069        );
 9070
 9071        // Ensure inserting the last character of a multi-byte bracket pair
 9072        // doesn't surround the selections with the bracket.
 9073        editor.handle_input("*", window, cx);
 9074        assert_eq!(
 9075            editor.text(cx),
 9076            "
 9077                *
 9078                *
 9079                *
 9080            "
 9081            .unindent()
 9082        );
 9083        assert_eq!(
 9084            editor.selections.display_ranges(cx),
 9085            [
 9086                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9087                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9088                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9089            ]
 9090        );
 9091    });
 9092}
 9093
 9094#[gpui::test]
 9095async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9096    init_test(cx, |_| {});
 9097
 9098    let language = Arc::new(Language::new(
 9099        LanguageConfig {
 9100            brackets: BracketPairConfig {
 9101                pairs: vec![BracketPair {
 9102                    start: "{".to_string(),
 9103                    end: "}".to_string(),
 9104                    close: true,
 9105                    surround: true,
 9106                    newline: true,
 9107                }],
 9108                ..Default::default()
 9109            },
 9110            autoclose_before: "}".to_string(),
 9111            ..Default::default()
 9112        },
 9113        Some(tree_sitter_rust::LANGUAGE.into()),
 9114    ));
 9115
 9116    let text = r#"
 9117        a
 9118        b
 9119        c
 9120    "#
 9121    .unindent();
 9122
 9123    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9124    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9125    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9126    editor
 9127        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9128        .await;
 9129
 9130    editor.update_in(cx, |editor, window, cx| {
 9131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9132            s.select_ranges([
 9133                Point::new(0, 1)..Point::new(0, 1),
 9134                Point::new(1, 1)..Point::new(1, 1),
 9135                Point::new(2, 1)..Point::new(2, 1),
 9136            ])
 9137        });
 9138
 9139        editor.handle_input("{", window, cx);
 9140        editor.handle_input("{", window, cx);
 9141        editor.handle_input("_", window, cx);
 9142        assert_eq!(
 9143            editor.text(cx),
 9144            "
 9145                a{{_}}
 9146                b{{_}}
 9147                c{{_}}
 9148            "
 9149            .unindent()
 9150        );
 9151        assert_eq!(
 9152            editor.selections.ranges::<Point>(cx),
 9153            [
 9154                Point::new(0, 4)..Point::new(0, 4),
 9155                Point::new(1, 4)..Point::new(1, 4),
 9156                Point::new(2, 4)..Point::new(2, 4)
 9157            ]
 9158        );
 9159
 9160        editor.backspace(&Default::default(), window, cx);
 9161        editor.backspace(&Default::default(), window, cx);
 9162        assert_eq!(
 9163            editor.text(cx),
 9164            "
 9165                a{}
 9166                b{}
 9167                c{}
 9168            "
 9169            .unindent()
 9170        );
 9171        assert_eq!(
 9172            editor.selections.ranges::<Point>(cx),
 9173            [
 9174                Point::new(0, 2)..Point::new(0, 2),
 9175                Point::new(1, 2)..Point::new(1, 2),
 9176                Point::new(2, 2)..Point::new(2, 2)
 9177            ]
 9178        );
 9179
 9180        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9181        assert_eq!(
 9182            editor.text(cx),
 9183            "
 9184                a
 9185                b
 9186                c
 9187            "
 9188            .unindent()
 9189        );
 9190        assert_eq!(
 9191            editor.selections.ranges::<Point>(cx),
 9192            [
 9193                Point::new(0, 1)..Point::new(0, 1),
 9194                Point::new(1, 1)..Point::new(1, 1),
 9195                Point::new(2, 1)..Point::new(2, 1)
 9196            ]
 9197        );
 9198    });
 9199}
 9200
 9201#[gpui::test]
 9202async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9203    init_test(cx, |settings| {
 9204        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9205    });
 9206
 9207    let mut cx = EditorTestContext::new(cx).await;
 9208
 9209    let language = Arc::new(Language::new(
 9210        LanguageConfig {
 9211            brackets: BracketPairConfig {
 9212                pairs: vec![
 9213                    BracketPair {
 9214                        start: "{".to_string(),
 9215                        end: "}".to_string(),
 9216                        close: true,
 9217                        surround: true,
 9218                        newline: true,
 9219                    },
 9220                    BracketPair {
 9221                        start: "(".to_string(),
 9222                        end: ")".to_string(),
 9223                        close: true,
 9224                        surround: true,
 9225                        newline: true,
 9226                    },
 9227                    BracketPair {
 9228                        start: "[".to_string(),
 9229                        end: "]".to_string(),
 9230                        close: false,
 9231                        surround: true,
 9232                        newline: true,
 9233                    },
 9234                ],
 9235                ..Default::default()
 9236            },
 9237            autoclose_before: "})]".to_string(),
 9238            ..Default::default()
 9239        },
 9240        Some(tree_sitter_rust::LANGUAGE.into()),
 9241    ));
 9242
 9243    cx.language_registry().add(language.clone());
 9244    cx.update_buffer(|buffer, cx| {
 9245        buffer.set_language(Some(language), cx);
 9246    });
 9247
 9248    cx.set_state(
 9249        &"
 9250            {(ˇ)}
 9251            [[ˇ]]
 9252            {(ˇ)}
 9253        "
 9254        .unindent(),
 9255    );
 9256
 9257    cx.update_editor(|editor, window, cx| {
 9258        editor.backspace(&Default::default(), window, cx);
 9259        editor.backspace(&Default::default(), window, cx);
 9260    });
 9261
 9262    cx.assert_editor_state(
 9263        &"
 9264            ˇ
 9265            ˇ]]
 9266            ˇ
 9267        "
 9268        .unindent(),
 9269    );
 9270
 9271    cx.update_editor(|editor, window, cx| {
 9272        editor.handle_input("{", window, cx);
 9273        editor.handle_input("{", window, cx);
 9274        editor.move_right(&MoveRight, window, cx);
 9275        editor.move_right(&MoveRight, window, cx);
 9276        editor.move_left(&MoveLeft, window, cx);
 9277        editor.move_left(&MoveLeft, window, cx);
 9278        editor.backspace(&Default::default(), window, cx);
 9279    });
 9280
 9281    cx.assert_editor_state(
 9282        &"
 9283            {ˇ}
 9284            {ˇ}]]
 9285            {ˇ}
 9286        "
 9287        .unindent(),
 9288    );
 9289
 9290    cx.update_editor(|editor, window, cx| {
 9291        editor.backspace(&Default::default(), window, cx);
 9292    });
 9293
 9294    cx.assert_editor_state(
 9295        &"
 9296            ˇ
 9297            ˇ]]
 9298            ˇ
 9299        "
 9300        .unindent(),
 9301    );
 9302}
 9303
 9304#[gpui::test]
 9305async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9306    init_test(cx, |_| {});
 9307
 9308    let language = Arc::new(Language::new(
 9309        LanguageConfig::default(),
 9310        Some(tree_sitter_rust::LANGUAGE.into()),
 9311    ));
 9312
 9313    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9314    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9315    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9316    editor
 9317        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9318        .await;
 9319
 9320    editor.update_in(cx, |editor, window, cx| {
 9321        editor.set_auto_replace_emoji_shortcode(true);
 9322
 9323        editor.handle_input("Hello ", window, cx);
 9324        editor.handle_input(":wave", window, cx);
 9325        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9326
 9327        editor.handle_input(":", window, cx);
 9328        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9329
 9330        editor.handle_input(" :smile", window, cx);
 9331        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9332
 9333        editor.handle_input(":", window, cx);
 9334        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9335
 9336        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9337        editor.handle_input(":wave", window, cx);
 9338        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9339
 9340        editor.handle_input(":", window, cx);
 9341        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9342
 9343        editor.handle_input(":1", window, cx);
 9344        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9345
 9346        editor.handle_input(":", window, cx);
 9347        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9348
 9349        // Ensure shortcode does not get replaced when it is part of a word
 9350        editor.handle_input(" Test:wave", window, cx);
 9351        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9352
 9353        editor.handle_input(":", window, cx);
 9354        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9355
 9356        editor.set_auto_replace_emoji_shortcode(false);
 9357
 9358        // Ensure shortcode does not get replaced when auto replace is off
 9359        editor.handle_input(" :wave", window, cx);
 9360        assert_eq!(
 9361            editor.text(cx),
 9362            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9363        );
 9364
 9365        editor.handle_input(":", window, cx);
 9366        assert_eq!(
 9367            editor.text(cx),
 9368            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9369        );
 9370    });
 9371}
 9372
 9373#[gpui::test]
 9374async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9375    init_test(cx, |_| {});
 9376
 9377    let (text, insertion_ranges) = marked_text_ranges(
 9378        indoc! {"
 9379            ˇ
 9380        "},
 9381        false,
 9382    );
 9383
 9384    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9385    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9386
 9387    _ = editor.update_in(cx, |editor, window, cx| {
 9388        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9389
 9390        editor
 9391            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9392            .unwrap();
 9393
 9394        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9395            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9396            assert_eq!(editor.text(cx), expected_text);
 9397            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9398        }
 9399
 9400        assert(
 9401            editor,
 9402            cx,
 9403            indoc! {"
 9404            type «» =•
 9405            "},
 9406        );
 9407
 9408        assert!(editor.context_menu_visible(), "There should be a matches");
 9409    });
 9410}
 9411
 9412#[gpui::test]
 9413async fn test_snippets(cx: &mut TestAppContext) {
 9414    init_test(cx, |_| {});
 9415
 9416    let mut cx = EditorTestContext::new(cx).await;
 9417
 9418    cx.set_state(indoc! {"
 9419        a.ˇ b
 9420        a.ˇ b
 9421        a.ˇ b
 9422    "});
 9423
 9424    cx.update_editor(|editor, window, cx| {
 9425        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9426        let insertion_ranges = editor
 9427            .selections
 9428            .all(cx)
 9429            .iter()
 9430            .map(|s| s.range().clone())
 9431            .collect::<Vec<_>>();
 9432        editor
 9433            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9434            .unwrap();
 9435    });
 9436
 9437    cx.assert_editor_state(indoc! {"
 9438        a.f(«oneˇ», two, «threeˇ») b
 9439        a.f(«oneˇ», two, «threeˇ») b
 9440        a.f(«oneˇ», two, «threeˇ») b
 9441    "});
 9442
 9443    // Can't move earlier than the first tab stop
 9444    cx.update_editor(|editor, window, cx| {
 9445        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9446    });
 9447    cx.assert_editor_state(indoc! {"
 9448        a.f(«oneˇ», two, «threeˇ») b
 9449        a.f(«oneˇ», two, «threeˇ») b
 9450        a.f(«oneˇ», two, «threeˇ») b
 9451    "});
 9452
 9453    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9454    cx.assert_editor_state(indoc! {"
 9455        a.f(one, «twoˇ», three) b
 9456        a.f(one, «twoˇ», three) b
 9457        a.f(one, «twoˇ», three) b
 9458    "});
 9459
 9460    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9461    cx.assert_editor_state(indoc! {"
 9462        a.f(«oneˇ», two, «threeˇ») b
 9463        a.f(«oneˇ», two, «threeˇ») b
 9464        a.f(«oneˇ», two, «threeˇ») b
 9465    "});
 9466
 9467    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9468    cx.assert_editor_state(indoc! {"
 9469        a.f(one, «twoˇ», three) b
 9470        a.f(one, «twoˇ», three) b
 9471        a.f(one, «twoˇ», three) b
 9472    "});
 9473    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9474    cx.assert_editor_state(indoc! {"
 9475        a.f(one, two, three)ˇ b
 9476        a.f(one, two, three)ˇ b
 9477        a.f(one, two, three)ˇ b
 9478    "});
 9479
 9480    // As soon as the last tab stop is reached, snippet state is gone
 9481    cx.update_editor(|editor, window, cx| {
 9482        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9483    });
 9484    cx.assert_editor_state(indoc! {"
 9485        a.f(one, two, three)ˇ b
 9486        a.f(one, two, three)ˇ b
 9487        a.f(one, two, three)ˇ b
 9488    "});
 9489}
 9490
 9491#[gpui::test]
 9492async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9493    init_test(cx, |_| {});
 9494
 9495    let mut cx = EditorTestContext::new(cx).await;
 9496
 9497    cx.update_editor(|editor, window, cx| {
 9498        let snippet = Snippet::parse(indoc! {"
 9499            /*
 9500             * Multiline comment with leading indentation
 9501             *
 9502             * $1
 9503             */
 9504            $0"})
 9505        .unwrap();
 9506        let insertion_ranges = editor
 9507            .selections
 9508            .all(cx)
 9509            .iter()
 9510            .map(|s| s.range().clone())
 9511            .collect::<Vec<_>>();
 9512        editor
 9513            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9514            .unwrap();
 9515    });
 9516
 9517    cx.assert_editor_state(indoc! {"
 9518        /*
 9519         * Multiline comment with leading indentation
 9520         *
 9521         * ˇ
 9522         */
 9523    "});
 9524
 9525    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9526    cx.assert_editor_state(indoc! {"
 9527        /*
 9528         * Multiline comment with leading indentation
 9529         *
 9530         *•
 9531         */
 9532        ˇ"});
 9533}
 9534
 9535#[gpui::test]
 9536async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9537    init_test(cx, |_| {});
 9538
 9539    let fs = FakeFs::new(cx.executor());
 9540    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9541
 9542    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9543
 9544    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9545    language_registry.add(rust_lang());
 9546    let mut fake_servers = language_registry.register_fake_lsp(
 9547        "Rust",
 9548        FakeLspAdapter {
 9549            capabilities: lsp::ServerCapabilities {
 9550                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9551                ..Default::default()
 9552            },
 9553            ..Default::default()
 9554        },
 9555    );
 9556
 9557    let buffer = project
 9558        .update(cx, |project, cx| {
 9559            project.open_local_buffer(path!("/file.rs"), cx)
 9560        })
 9561        .await
 9562        .unwrap();
 9563
 9564    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9565    let (editor, cx) = cx.add_window_view(|window, cx| {
 9566        build_editor_with_project(project.clone(), buffer, window, cx)
 9567    });
 9568    editor.update_in(cx, |editor, window, cx| {
 9569        editor.set_text("one\ntwo\nthree\n", window, cx)
 9570    });
 9571    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9572
 9573    cx.executor().start_waiting();
 9574    let fake_server = fake_servers.next().await.unwrap();
 9575
 9576    {
 9577        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9578            move |params, _| async move {
 9579                assert_eq!(
 9580                    params.text_document.uri,
 9581                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9582                );
 9583                assert_eq!(params.options.tab_size, 4);
 9584                Ok(Some(vec![lsp::TextEdit::new(
 9585                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9586                    ", ".to_string(),
 9587                )]))
 9588            },
 9589        );
 9590        let save = editor
 9591            .update_in(cx, |editor, window, cx| {
 9592                editor.save(
 9593                    SaveOptions {
 9594                        format: true,
 9595                        autosave: false,
 9596                    },
 9597                    project.clone(),
 9598                    window,
 9599                    cx,
 9600                )
 9601            })
 9602            .unwrap();
 9603        cx.executor().start_waiting();
 9604        save.await;
 9605
 9606        assert_eq!(
 9607            editor.update(cx, |editor, cx| editor.text(cx)),
 9608            "one, two\nthree\n"
 9609        );
 9610        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9611    }
 9612
 9613    {
 9614        editor.update_in(cx, |editor, window, cx| {
 9615            editor.set_text("one\ntwo\nthree\n", window, cx)
 9616        });
 9617        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9618
 9619        // Ensure we can still save even if formatting hangs.
 9620        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9621            move |params, _| async move {
 9622                assert_eq!(
 9623                    params.text_document.uri,
 9624                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9625                );
 9626                futures::future::pending::<()>().await;
 9627                unreachable!()
 9628            },
 9629        );
 9630        let save = editor
 9631            .update_in(cx, |editor, window, cx| {
 9632                editor.save(
 9633                    SaveOptions {
 9634                        format: true,
 9635                        autosave: false,
 9636                    },
 9637                    project.clone(),
 9638                    window,
 9639                    cx,
 9640                )
 9641            })
 9642            .unwrap();
 9643        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9644        cx.executor().start_waiting();
 9645        save.await;
 9646        assert_eq!(
 9647            editor.update(cx, |editor, cx| editor.text(cx)),
 9648            "one\ntwo\nthree\n"
 9649        );
 9650    }
 9651
 9652    // Set rust language override and assert overridden tabsize is sent to language server
 9653    update_test_language_settings(cx, |settings| {
 9654        settings.languages.0.insert(
 9655            "Rust".into(),
 9656            LanguageSettingsContent {
 9657                tab_size: NonZeroU32::new(8),
 9658                ..Default::default()
 9659            },
 9660        );
 9661    });
 9662
 9663    {
 9664        editor.update_in(cx, |editor, window, cx| {
 9665            editor.set_text("somehting_new\n", window, cx)
 9666        });
 9667        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9668        let _formatting_request_signal = fake_server
 9669            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9670                assert_eq!(
 9671                    params.text_document.uri,
 9672                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9673                );
 9674                assert_eq!(params.options.tab_size, 8);
 9675                Ok(Some(vec![]))
 9676            });
 9677        let save = editor
 9678            .update_in(cx, |editor, window, cx| {
 9679                editor.save(
 9680                    SaveOptions {
 9681                        format: true,
 9682                        autosave: false,
 9683                    },
 9684                    project.clone(),
 9685                    window,
 9686                    cx,
 9687                )
 9688            })
 9689            .unwrap();
 9690        cx.executor().start_waiting();
 9691        save.await;
 9692    }
 9693}
 9694
 9695#[gpui::test]
 9696async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9697    init_test(cx, |settings| {
 9698        settings.defaults.ensure_final_newline_on_save = Some(false);
 9699    });
 9700
 9701    let fs = FakeFs::new(cx.executor());
 9702    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9703
 9704    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9705
 9706    let buffer = project
 9707        .update(cx, |project, cx| {
 9708            project.open_local_buffer(path!("/file.txt"), cx)
 9709        })
 9710        .await
 9711        .unwrap();
 9712
 9713    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9714    let (editor, cx) = cx.add_window_view(|window, cx| {
 9715        build_editor_with_project(project.clone(), buffer, window, cx)
 9716    });
 9717    editor.update_in(cx, |editor, window, cx| {
 9718        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9719            s.select_ranges([0..0])
 9720        });
 9721    });
 9722    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9723
 9724    editor.update_in(cx, |editor, window, cx| {
 9725        editor.handle_input("\n", window, cx)
 9726    });
 9727    cx.run_until_parked();
 9728    save(&editor, &project, cx).await;
 9729    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9730
 9731    editor.update_in(cx, |editor, window, cx| {
 9732        editor.undo(&Default::default(), window, cx);
 9733    });
 9734    save(&editor, &project, cx).await;
 9735    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9736
 9737    editor.update_in(cx, |editor, window, cx| {
 9738        editor.redo(&Default::default(), window, cx);
 9739    });
 9740    cx.run_until_parked();
 9741    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9742
 9743    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9744        let save = editor
 9745            .update_in(cx, |editor, window, cx| {
 9746                editor.save(
 9747                    SaveOptions {
 9748                        format: true,
 9749                        autosave: false,
 9750                    },
 9751                    project.clone(),
 9752                    window,
 9753                    cx,
 9754                )
 9755            })
 9756            .unwrap();
 9757        cx.executor().start_waiting();
 9758        save.await;
 9759        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9760    }
 9761}
 9762
 9763#[gpui::test]
 9764async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9765    init_test(cx, |_| {});
 9766
 9767    let cols = 4;
 9768    let rows = 10;
 9769    let sample_text_1 = sample_text(rows, cols, 'a');
 9770    assert_eq!(
 9771        sample_text_1,
 9772        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9773    );
 9774    let sample_text_2 = sample_text(rows, cols, 'l');
 9775    assert_eq!(
 9776        sample_text_2,
 9777        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9778    );
 9779    let sample_text_3 = sample_text(rows, cols, 'v');
 9780    assert_eq!(
 9781        sample_text_3,
 9782        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9783    );
 9784
 9785    let fs = FakeFs::new(cx.executor());
 9786    fs.insert_tree(
 9787        path!("/a"),
 9788        json!({
 9789            "main.rs": sample_text_1,
 9790            "other.rs": sample_text_2,
 9791            "lib.rs": sample_text_3,
 9792        }),
 9793    )
 9794    .await;
 9795
 9796    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9797    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9798    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9799
 9800    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9801    language_registry.add(rust_lang());
 9802    let mut fake_servers = language_registry.register_fake_lsp(
 9803        "Rust",
 9804        FakeLspAdapter {
 9805            capabilities: lsp::ServerCapabilities {
 9806                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9807                ..Default::default()
 9808            },
 9809            ..Default::default()
 9810        },
 9811    );
 9812
 9813    let worktree = project.update(cx, |project, cx| {
 9814        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9815        assert_eq!(worktrees.len(), 1);
 9816        worktrees.pop().unwrap()
 9817    });
 9818    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9819
 9820    let buffer_1 = project
 9821        .update(cx, |project, cx| {
 9822            project.open_buffer((worktree_id, "main.rs"), cx)
 9823        })
 9824        .await
 9825        .unwrap();
 9826    let buffer_2 = project
 9827        .update(cx, |project, cx| {
 9828            project.open_buffer((worktree_id, "other.rs"), cx)
 9829        })
 9830        .await
 9831        .unwrap();
 9832    let buffer_3 = project
 9833        .update(cx, |project, cx| {
 9834            project.open_buffer((worktree_id, "lib.rs"), cx)
 9835        })
 9836        .await
 9837        .unwrap();
 9838
 9839    let multi_buffer = cx.new(|cx| {
 9840        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9841        multi_buffer.push_excerpts(
 9842            buffer_1.clone(),
 9843            [
 9844                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9845                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9846                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9847            ],
 9848            cx,
 9849        );
 9850        multi_buffer.push_excerpts(
 9851            buffer_2.clone(),
 9852            [
 9853                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9854                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9855                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9856            ],
 9857            cx,
 9858        );
 9859        multi_buffer.push_excerpts(
 9860            buffer_3.clone(),
 9861            [
 9862                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9863                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9864                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9865            ],
 9866            cx,
 9867        );
 9868        multi_buffer
 9869    });
 9870    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9871        Editor::new(
 9872            EditorMode::full(),
 9873            multi_buffer,
 9874            Some(project.clone()),
 9875            window,
 9876            cx,
 9877        )
 9878    });
 9879
 9880    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9881        editor.change_selections(
 9882            SelectionEffects::scroll(Autoscroll::Next),
 9883            window,
 9884            cx,
 9885            |s| s.select_ranges(Some(1..2)),
 9886        );
 9887        editor.insert("|one|two|three|", window, cx);
 9888    });
 9889    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9890    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9891        editor.change_selections(
 9892            SelectionEffects::scroll(Autoscroll::Next),
 9893            window,
 9894            cx,
 9895            |s| s.select_ranges(Some(60..70)),
 9896        );
 9897        editor.insert("|four|five|six|", window, cx);
 9898    });
 9899    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9900
 9901    // First two buffers should be edited, but not the third one.
 9902    assert_eq!(
 9903        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9904        "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}",
 9905    );
 9906    buffer_1.update(cx, |buffer, _| {
 9907        assert!(buffer.is_dirty());
 9908        assert_eq!(
 9909            buffer.text(),
 9910            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9911        )
 9912    });
 9913    buffer_2.update(cx, |buffer, _| {
 9914        assert!(buffer.is_dirty());
 9915        assert_eq!(
 9916            buffer.text(),
 9917            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9918        )
 9919    });
 9920    buffer_3.update(cx, |buffer, _| {
 9921        assert!(!buffer.is_dirty());
 9922        assert_eq!(buffer.text(), sample_text_3,)
 9923    });
 9924    cx.executor().run_until_parked();
 9925
 9926    cx.executor().start_waiting();
 9927    let save = multi_buffer_editor
 9928        .update_in(cx, |editor, window, cx| {
 9929            editor.save(
 9930                SaveOptions {
 9931                    format: true,
 9932                    autosave: false,
 9933                },
 9934                project.clone(),
 9935                window,
 9936                cx,
 9937            )
 9938        })
 9939        .unwrap();
 9940
 9941    let fake_server = fake_servers.next().await.unwrap();
 9942    fake_server
 9943        .server
 9944        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9945            Ok(Some(vec![lsp::TextEdit::new(
 9946                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9947                format!("[{} formatted]", params.text_document.uri),
 9948            )]))
 9949        })
 9950        .detach();
 9951    save.await;
 9952
 9953    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9954    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9955    assert_eq!(
 9956        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9957        uri!(
 9958            "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}"
 9959        ),
 9960    );
 9961    buffer_1.update(cx, |buffer, _| {
 9962        assert!(!buffer.is_dirty());
 9963        assert_eq!(
 9964            buffer.text(),
 9965            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9966        )
 9967    });
 9968    buffer_2.update(cx, |buffer, _| {
 9969        assert!(!buffer.is_dirty());
 9970        assert_eq!(
 9971            buffer.text(),
 9972            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9973        )
 9974    });
 9975    buffer_3.update(cx, |buffer, _| {
 9976        assert!(!buffer.is_dirty());
 9977        assert_eq!(buffer.text(), sample_text_3,)
 9978    });
 9979}
 9980
 9981#[gpui::test]
 9982async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9983    init_test(cx, |_| {});
 9984
 9985    let fs = FakeFs::new(cx.executor());
 9986    fs.insert_tree(
 9987        path!("/dir"),
 9988        json!({
 9989            "file1.rs": "fn main() { println!(\"hello\"); }",
 9990            "file2.rs": "fn test() { println!(\"test\"); }",
 9991            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9992        }),
 9993    )
 9994    .await;
 9995
 9996    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9997    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9998    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9999
10000    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10001    language_registry.add(rust_lang());
10002
10003    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10004    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10005
10006    // Open three buffers
10007    let buffer_1 = project
10008        .update(cx, |project, cx| {
10009            project.open_buffer((worktree_id, "file1.rs"), cx)
10010        })
10011        .await
10012        .unwrap();
10013    let buffer_2 = project
10014        .update(cx, |project, cx| {
10015            project.open_buffer((worktree_id, "file2.rs"), cx)
10016        })
10017        .await
10018        .unwrap();
10019    let buffer_3 = project
10020        .update(cx, |project, cx| {
10021            project.open_buffer((worktree_id, "file3.rs"), cx)
10022        })
10023        .await
10024        .unwrap();
10025
10026    // Create a multi-buffer with all three buffers
10027    let multi_buffer = cx.new(|cx| {
10028        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10029        multi_buffer.push_excerpts(
10030            buffer_1.clone(),
10031            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10032            cx,
10033        );
10034        multi_buffer.push_excerpts(
10035            buffer_2.clone(),
10036            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10037            cx,
10038        );
10039        multi_buffer.push_excerpts(
10040            buffer_3.clone(),
10041            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10042            cx,
10043        );
10044        multi_buffer
10045    });
10046
10047    let editor = cx.new_window_entity(|window, cx| {
10048        Editor::new(
10049            EditorMode::full(),
10050            multi_buffer,
10051            Some(project.clone()),
10052            window,
10053            cx,
10054        )
10055    });
10056
10057    // Edit only the first buffer
10058    editor.update_in(cx, |editor, window, cx| {
10059        editor.change_selections(
10060            SelectionEffects::scroll(Autoscroll::Next),
10061            window,
10062            cx,
10063            |s| s.select_ranges(Some(10..10)),
10064        );
10065        editor.insert("// edited", window, cx);
10066    });
10067
10068    // Verify that only buffer 1 is dirty
10069    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10070    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10071    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10072
10073    // Get write counts after file creation (files were created with initial content)
10074    // We expect each file to have been written once during creation
10075    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10076    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10077    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10078
10079    // Perform autosave
10080    let save_task = editor.update_in(cx, |editor, window, cx| {
10081        editor.save(
10082            SaveOptions {
10083                format: true,
10084                autosave: true,
10085            },
10086            project.clone(),
10087            window,
10088            cx,
10089        )
10090    });
10091    save_task.await.unwrap();
10092
10093    // Only the dirty buffer should have been saved
10094    assert_eq!(
10095        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10096        1,
10097        "Buffer 1 was dirty, so it should have been written once during autosave"
10098    );
10099    assert_eq!(
10100        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10101        0,
10102        "Buffer 2 was clean, so it should not have been written during autosave"
10103    );
10104    assert_eq!(
10105        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10106        0,
10107        "Buffer 3 was clean, so it should not have been written during autosave"
10108    );
10109
10110    // Verify buffer states after autosave
10111    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10112    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10113    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10114
10115    // Now perform a manual save (format = true)
10116    let save_task = editor.update_in(cx, |editor, window, cx| {
10117        editor.save(
10118            SaveOptions {
10119                format: true,
10120                autosave: false,
10121            },
10122            project.clone(),
10123            window,
10124            cx,
10125        )
10126    });
10127    save_task.await.unwrap();
10128
10129    // During manual save, clean buffers don't get written to disk
10130    // They just get did_save called for language server notifications
10131    assert_eq!(
10132        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10133        1,
10134        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10135    );
10136    assert_eq!(
10137        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10138        0,
10139        "Buffer 2 should not have been written at all"
10140    );
10141    assert_eq!(
10142        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10143        0,
10144        "Buffer 3 should not have been written at all"
10145    );
10146}
10147
10148#[gpui::test]
10149async fn test_range_format_during_save(cx: &mut TestAppContext) {
10150    init_test(cx, |_| {});
10151
10152    let fs = FakeFs::new(cx.executor());
10153    fs.insert_file(path!("/file.rs"), Default::default()).await;
10154
10155    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10156
10157    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10158    language_registry.add(rust_lang());
10159    let mut fake_servers = language_registry.register_fake_lsp(
10160        "Rust",
10161        FakeLspAdapter {
10162            capabilities: lsp::ServerCapabilities {
10163                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10164                ..Default::default()
10165            },
10166            ..Default::default()
10167        },
10168    );
10169
10170    let buffer = project
10171        .update(cx, |project, cx| {
10172            project.open_local_buffer(path!("/file.rs"), cx)
10173        })
10174        .await
10175        .unwrap();
10176
10177    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10178    let (editor, cx) = cx.add_window_view(|window, cx| {
10179        build_editor_with_project(project.clone(), buffer, window, cx)
10180    });
10181    editor.update_in(cx, |editor, window, cx| {
10182        editor.set_text("one\ntwo\nthree\n", window, cx)
10183    });
10184    assert!(cx.read(|cx| editor.is_dirty(cx)));
10185
10186    cx.executor().start_waiting();
10187    let fake_server = fake_servers.next().await.unwrap();
10188
10189    let save = editor
10190        .update_in(cx, |editor, window, cx| {
10191            editor.save(
10192                SaveOptions {
10193                    format: true,
10194                    autosave: false,
10195                },
10196                project.clone(),
10197                window,
10198                cx,
10199            )
10200        })
10201        .unwrap();
10202    fake_server
10203        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10204            assert_eq!(
10205                params.text_document.uri,
10206                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10207            );
10208            assert_eq!(params.options.tab_size, 4);
10209            Ok(Some(vec![lsp::TextEdit::new(
10210                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10211                ", ".to_string(),
10212            )]))
10213        })
10214        .next()
10215        .await;
10216    cx.executor().start_waiting();
10217    save.await;
10218    assert_eq!(
10219        editor.update(cx, |editor, cx| editor.text(cx)),
10220        "one, two\nthree\n"
10221    );
10222    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10223
10224    editor.update_in(cx, |editor, window, cx| {
10225        editor.set_text("one\ntwo\nthree\n", window, cx)
10226    });
10227    assert!(cx.read(|cx| editor.is_dirty(cx)));
10228
10229    // Ensure we can still save even if formatting hangs.
10230    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10231        move |params, _| async move {
10232            assert_eq!(
10233                params.text_document.uri,
10234                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10235            );
10236            futures::future::pending::<()>().await;
10237            unreachable!()
10238        },
10239    );
10240    let save = editor
10241        .update_in(cx, |editor, window, cx| {
10242            editor.save(
10243                SaveOptions {
10244                    format: true,
10245                    autosave: false,
10246                },
10247                project.clone(),
10248                window,
10249                cx,
10250            )
10251        })
10252        .unwrap();
10253    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10254    cx.executor().start_waiting();
10255    save.await;
10256    assert_eq!(
10257        editor.update(cx, |editor, cx| editor.text(cx)),
10258        "one\ntwo\nthree\n"
10259    );
10260    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10261
10262    // For non-dirty buffer, no formatting request should be sent
10263    let save = editor
10264        .update_in(cx, |editor, window, cx| {
10265            editor.save(
10266                SaveOptions {
10267                    format: false,
10268                    autosave: false,
10269                },
10270                project.clone(),
10271                window,
10272                cx,
10273            )
10274        })
10275        .unwrap();
10276    let _pending_format_request = fake_server
10277        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10278            panic!("Should not be invoked");
10279        })
10280        .next();
10281    cx.executor().start_waiting();
10282    save.await;
10283
10284    // Set Rust language override and assert overridden tabsize is sent to language server
10285    update_test_language_settings(cx, |settings| {
10286        settings.languages.0.insert(
10287            "Rust".into(),
10288            LanguageSettingsContent {
10289                tab_size: NonZeroU32::new(8),
10290                ..Default::default()
10291            },
10292        );
10293    });
10294
10295    editor.update_in(cx, |editor, window, cx| {
10296        editor.set_text("somehting_new\n", window, cx)
10297    });
10298    assert!(cx.read(|cx| editor.is_dirty(cx)));
10299    let save = editor
10300        .update_in(cx, |editor, window, cx| {
10301            editor.save(
10302                SaveOptions {
10303                    format: true,
10304                    autosave: false,
10305                },
10306                project.clone(),
10307                window,
10308                cx,
10309            )
10310        })
10311        .unwrap();
10312    fake_server
10313        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10314            assert_eq!(
10315                params.text_document.uri,
10316                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10317            );
10318            assert_eq!(params.options.tab_size, 8);
10319            Ok(Some(Vec::new()))
10320        })
10321        .next()
10322        .await;
10323    save.await;
10324}
10325
10326#[gpui::test]
10327async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10328    init_test(cx, |settings| {
10329        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10330            Formatter::LanguageServer { name: None },
10331        )))
10332    });
10333
10334    let fs = FakeFs::new(cx.executor());
10335    fs.insert_file(path!("/file.rs"), Default::default()).await;
10336
10337    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10338
10339    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10340    language_registry.add(Arc::new(Language::new(
10341        LanguageConfig {
10342            name: "Rust".into(),
10343            matcher: LanguageMatcher {
10344                path_suffixes: vec!["rs".to_string()],
10345                ..Default::default()
10346            },
10347            ..LanguageConfig::default()
10348        },
10349        Some(tree_sitter_rust::LANGUAGE.into()),
10350    )));
10351    update_test_language_settings(cx, |settings| {
10352        // Enable Prettier formatting for the same buffer, and ensure
10353        // LSP is called instead of Prettier.
10354        settings.defaults.prettier = Some(PrettierSettings {
10355            allowed: true,
10356            ..PrettierSettings::default()
10357        });
10358    });
10359    let mut fake_servers = language_registry.register_fake_lsp(
10360        "Rust",
10361        FakeLspAdapter {
10362            capabilities: lsp::ServerCapabilities {
10363                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10364                ..Default::default()
10365            },
10366            ..Default::default()
10367        },
10368    );
10369
10370    let buffer = project
10371        .update(cx, |project, cx| {
10372            project.open_local_buffer(path!("/file.rs"), cx)
10373        })
10374        .await
10375        .unwrap();
10376
10377    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10378    let (editor, cx) = cx.add_window_view(|window, cx| {
10379        build_editor_with_project(project.clone(), buffer, window, cx)
10380    });
10381    editor.update_in(cx, |editor, window, cx| {
10382        editor.set_text("one\ntwo\nthree\n", window, cx)
10383    });
10384
10385    cx.executor().start_waiting();
10386    let fake_server = fake_servers.next().await.unwrap();
10387
10388    let format = editor
10389        .update_in(cx, |editor, window, cx| {
10390            editor.perform_format(
10391                project.clone(),
10392                FormatTrigger::Manual,
10393                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10394                window,
10395                cx,
10396            )
10397        })
10398        .unwrap();
10399    fake_server
10400        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10401            assert_eq!(
10402                params.text_document.uri,
10403                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10404            );
10405            assert_eq!(params.options.tab_size, 4);
10406            Ok(Some(vec![lsp::TextEdit::new(
10407                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10408                ", ".to_string(),
10409            )]))
10410        })
10411        .next()
10412        .await;
10413    cx.executor().start_waiting();
10414    format.await;
10415    assert_eq!(
10416        editor.update(cx, |editor, cx| editor.text(cx)),
10417        "one, two\nthree\n"
10418    );
10419
10420    editor.update_in(cx, |editor, window, cx| {
10421        editor.set_text("one\ntwo\nthree\n", window, cx)
10422    });
10423    // Ensure we don't lock if formatting hangs.
10424    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10425        move |params, _| async move {
10426            assert_eq!(
10427                params.text_document.uri,
10428                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10429            );
10430            futures::future::pending::<()>().await;
10431            unreachable!()
10432        },
10433    );
10434    let format = editor
10435        .update_in(cx, |editor, window, cx| {
10436            editor.perform_format(
10437                project,
10438                FormatTrigger::Manual,
10439                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10440                window,
10441                cx,
10442            )
10443        })
10444        .unwrap();
10445    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10446    cx.executor().start_waiting();
10447    format.await;
10448    assert_eq!(
10449        editor.update(cx, |editor, cx| editor.text(cx)),
10450        "one\ntwo\nthree\n"
10451    );
10452}
10453
10454#[gpui::test]
10455async fn test_multiple_formatters(cx: &mut TestAppContext) {
10456    init_test(cx, |settings| {
10457        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10458        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10459            Formatter::LanguageServer { name: None },
10460            Formatter::CodeActions(
10461                [
10462                    ("code-action-1".into(), true),
10463                    ("code-action-2".into(), true),
10464                ]
10465                .into_iter()
10466                .collect(),
10467            ),
10468        ])))
10469    });
10470
10471    let fs = FakeFs::new(cx.executor());
10472    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10473        .await;
10474
10475    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10476    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10477    language_registry.add(rust_lang());
10478
10479    let mut fake_servers = language_registry.register_fake_lsp(
10480        "Rust",
10481        FakeLspAdapter {
10482            capabilities: lsp::ServerCapabilities {
10483                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10484                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10485                    commands: vec!["the-command-for-code-action-1".into()],
10486                    ..Default::default()
10487                }),
10488                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10489                ..Default::default()
10490            },
10491            ..Default::default()
10492        },
10493    );
10494
10495    let buffer = project
10496        .update(cx, |project, cx| {
10497            project.open_local_buffer(path!("/file.rs"), cx)
10498        })
10499        .await
10500        .unwrap();
10501
10502    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10503    let (editor, cx) = cx.add_window_view(|window, cx| {
10504        build_editor_with_project(project.clone(), buffer, window, cx)
10505    });
10506
10507    cx.executor().start_waiting();
10508
10509    let fake_server = fake_servers.next().await.unwrap();
10510    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10511        move |_params, _| async move {
10512            Ok(Some(vec![lsp::TextEdit::new(
10513                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10514                "applied-formatting\n".to_string(),
10515            )]))
10516        },
10517    );
10518    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10519        move |params, _| async move {
10520            assert_eq!(
10521                params.context.only,
10522                Some(vec!["code-action-1".into(), "code-action-2".into()])
10523            );
10524            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10525            Ok(Some(vec![
10526                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10527                    kind: Some("code-action-1".into()),
10528                    edit: Some(lsp::WorkspaceEdit::new(
10529                        [(
10530                            uri.clone(),
10531                            vec![lsp::TextEdit::new(
10532                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10533                                "applied-code-action-1-edit\n".to_string(),
10534                            )],
10535                        )]
10536                        .into_iter()
10537                        .collect(),
10538                    )),
10539                    command: Some(lsp::Command {
10540                        command: "the-command-for-code-action-1".into(),
10541                        ..Default::default()
10542                    }),
10543                    ..Default::default()
10544                }),
10545                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10546                    kind: Some("code-action-2".into()),
10547                    edit: Some(lsp::WorkspaceEdit::new(
10548                        [(
10549                            uri.clone(),
10550                            vec![lsp::TextEdit::new(
10551                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10552                                "applied-code-action-2-edit\n".to_string(),
10553                            )],
10554                        )]
10555                        .into_iter()
10556                        .collect(),
10557                    )),
10558                    ..Default::default()
10559                }),
10560            ]))
10561        },
10562    );
10563
10564    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10565        move |params, _| async move { Ok(params) }
10566    });
10567
10568    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10569    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10570        let fake = fake_server.clone();
10571        let lock = command_lock.clone();
10572        move |params, _| {
10573            assert_eq!(params.command, "the-command-for-code-action-1");
10574            let fake = fake.clone();
10575            let lock = lock.clone();
10576            async move {
10577                lock.lock().await;
10578                fake.server
10579                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10580                        label: None,
10581                        edit: lsp::WorkspaceEdit {
10582                            changes: Some(
10583                                [(
10584                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10585                                    vec![lsp::TextEdit {
10586                                        range: lsp::Range::new(
10587                                            lsp::Position::new(0, 0),
10588                                            lsp::Position::new(0, 0),
10589                                        ),
10590                                        new_text: "applied-code-action-1-command\n".into(),
10591                                    }],
10592                                )]
10593                                .into_iter()
10594                                .collect(),
10595                            ),
10596                            ..Default::default()
10597                        },
10598                    })
10599                    .await
10600                    .into_response()
10601                    .unwrap();
10602                Ok(Some(json!(null)))
10603            }
10604        }
10605    });
10606
10607    cx.executor().start_waiting();
10608    editor
10609        .update_in(cx, |editor, window, cx| {
10610            editor.perform_format(
10611                project.clone(),
10612                FormatTrigger::Manual,
10613                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10614                window,
10615                cx,
10616            )
10617        })
10618        .unwrap()
10619        .await;
10620    editor.update(cx, |editor, cx| {
10621        assert_eq!(
10622            editor.text(cx),
10623            r#"
10624                applied-code-action-2-edit
10625                applied-code-action-1-command
10626                applied-code-action-1-edit
10627                applied-formatting
10628                one
10629                two
10630                three
10631            "#
10632            .unindent()
10633        );
10634    });
10635
10636    editor.update_in(cx, |editor, window, cx| {
10637        editor.undo(&Default::default(), window, cx);
10638        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10639    });
10640
10641    // Perform a manual edit while waiting for an LSP command
10642    // that's being run as part of a formatting code action.
10643    let lock_guard = command_lock.lock().await;
10644    let format = editor
10645        .update_in(cx, |editor, window, cx| {
10646            editor.perform_format(
10647                project.clone(),
10648                FormatTrigger::Manual,
10649                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10650                window,
10651                cx,
10652            )
10653        })
10654        .unwrap();
10655    cx.run_until_parked();
10656    editor.update(cx, |editor, cx| {
10657        assert_eq!(
10658            editor.text(cx),
10659            r#"
10660                applied-code-action-1-edit
10661                applied-formatting
10662                one
10663                two
10664                three
10665            "#
10666            .unindent()
10667        );
10668
10669        editor.buffer.update(cx, |buffer, cx| {
10670            let ix = buffer.len(cx);
10671            buffer.edit([(ix..ix, "edited\n")], None, cx);
10672        });
10673    });
10674
10675    // Allow the LSP command to proceed. Because the buffer was edited,
10676    // the second code action will not be run.
10677    drop(lock_guard);
10678    format.await;
10679    editor.update_in(cx, |editor, window, cx| {
10680        assert_eq!(
10681            editor.text(cx),
10682            r#"
10683                applied-code-action-1-command
10684                applied-code-action-1-edit
10685                applied-formatting
10686                one
10687                two
10688                three
10689                edited
10690            "#
10691            .unindent()
10692        );
10693
10694        // The manual edit is undone first, because it is the last thing the user did
10695        // (even though the command completed afterwards).
10696        editor.undo(&Default::default(), window, cx);
10697        assert_eq!(
10698            editor.text(cx),
10699            r#"
10700                applied-code-action-1-command
10701                applied-code-action-1-edit
10702                applied-formatting
10703                one
10704                two
10705                three
10706            "#
10707            .unindent()
10708        );
10709
10710        // All the formatting (including the command, which completed after the manual edit)
10711        // is undone together.
10712        editor.undo(&Default::default(), window, cx);
10713        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10714    });
10715}
10716
10717#[gpui::test]
10718async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10719    init_test(cx, |settings| {
10720        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10721            Formatter::LanguageServer { name: None },
10722        ])))
10723    });
10724
10725    let fs = FakeFs::new(cx.executor());
10726    fs.insert_file(path!("/file.ts"), Default::default()).await;
10727
10728    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10729
10730    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10731    language_registry.add(Arc::new(Language::new(
10732        LanguageConfig {
10733            name: "TypeScript".into(),
10734            matcher: LanguageMatcher {
10735                path_suffixes: vec!["ts".to_string()],
10736                ..Default::default()
10737            },
10738            ..LanguageConfig::default()
10739        },
10740        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10741    )));
10742    update_test_language_settings(cx, |settings| {
10743        settings.defaults.prettier = Some(PrettierSettings {
10744            allowed: true,
10745            ..PrettierSettings::default()
10746        });
10747    });
10748    let mut fake_servers = language_registry.register_fake_lsp(
10749        "TypeScript",
10750        FakeLspAdapter {
10751            capabilities: lsp::ServerCapabilities {
10752                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10753                ..Default::default()
10754            },
10755            ..Default::default()
10756        },
10757    );
10758
10759    let buffer = project
10760        .update(cx, |project, cx| {
10761            project.open_local_buffer(path!("/file.ts"), cx)
10762        })
10763        .await
10764        .unwrap();
10765
10766    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10767    let (editor, cx) = cx.add_window_view(|window, cx| {
10768        build_editor_with_project(project.clone(), buffer, window, cx)
10769    });
10770    editor.update_in(cx, |editor, window, cx| {
10771        editor.set_text(
10772            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10773            window,
10774            cx,
10775        )
10776    });
10777
10778    cx.executor().start_waiting();
10779    let fake_server = fake_servers.next().await.unwrap();
10780
10781    let format = editor
10782        .update_in(cx, |editor, window, cx| {
10783            editor.perform_code_action_kind(
10784                project.clone(),
10785                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10786                window,
10787                cx,
10788            )
10789        })
10790        .unwrap();
10791    fake_server
10792        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10793            assert_eq!(
10794                params.text_document.uri,
10795                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10796            );
10797            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10798                lsp::CodeAction {
10799                    title: "Organize Imports".to_string(),
10800                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10801                    edit: Some(lsp::WorkspaceEdit {
10802                        changes: Some(
10803                            [(
10804                                params.text_document.uri.clone(),
10805                                vec![lsp::TextEdit::new(
10806                                    lsp::Range::new(
10807                                        lsp::Position::new(1, 0),
10808                                        lsp::Position::new(2, 0),
10809                                    ),
10810                                    "".to_string(),
10811                                )],
10812                            )]
10813                            .into_iter()
10814                            .collect(),
10815                        ),
10816                        ..Default::default()
10817                    }),
10818                    ..Default::default()
10819                },
10820            )]))
10821        })
10822        .next()
10823        .await;
10824    cx.executor().start_waiting();
10825    format.await;
10826    assert_eq!(
10827        editor.update(cx, |editor, cx| editor.text(cx)),
10828        "import { a } from 'module';\n\nconst x = a;\n"
10829    );
10830
10831    editor.update_in(cx, |editor, window, cx| {
10832        editor.set_text(
10833            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10834            window,
10835            cx,
10836        )
10837    });
10838    // Ensure we don't lock if code action hangs.
10839    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10840        move |params, _| async move {
10841            assert_eq!(
10842                params.text_document.uri,
10843                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10844            );
10845            futures::future::pending::<()>().await;
10846            unreachable!()
10847        },
10848    );
10849    let format = editor
10850        .update_in(cx, |editor, window, cx| {
10851            editor.perform_code_action_kind(
10852                project,
10853                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10854                window,
10855                cx,
10856            )
10857        })
10858        .unwrap();
10859    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10860    cx.executor().start_waiting();
10861    format.await;
10862    assert_eq!(
10863        editor.update(cx, |editor, cx| editor.text(cx)),
10864        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10865    );
10866}
10867
10868#[gpui::test]
10869async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10870    init_test(cx, |_| {});
10871
10872    let mut cx = EditorLspTestContext::new_rust(
10873        lsp::ServerCapabilities {
10874            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10875            ..Default::default()
10876        },
10877        cx,
10878    )
10879    .await;
10880
10881    cx.set_state(indoc! {"
10882        one.twoˇ
10883    "});
10884
10885    // The format request takes a long time. When it completes, it inserts
10886    // a newline and an indent before the `.`
10887    cx.lsp
10888        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10889            let executor = cx.background_executor().clone();
10890            async move {
10891                executor.timer(Duration::from_millis(100)).await;
10892                Ok(Some(vec![lsp::TextEdit {
10893                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10894                    new_text: "\n    ".into(),
10895                }]))
10896            }
10897        });
10898
10899    // Submit a format request.
10900    let format_1 = cx
10901        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10902        .unwrap();
10903    cx.executor().run_until_parked();
10904
10905    // Submit a second format request.
10906    let format_2 = cx
10907        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10908        .unwrap();
10909    cx.executor().run_until_parked();
10910
10911    // Wait for both format requests to complete
10912    cx.executor().advance_clock(Duration::from_millis(200));
10913    cx.executor().start_waiting();
10914    format_1.await.unwrap();
10915    cx.executor().start_waiting();
10916    format_2.await.unwrap();
10917
10918    // The formatting edits only happens once.
10919    cx.assert_editor_state(indoc! {"
10920        one
10921            .twoˇ
10922    "});
10923}
10924
10925#[gpui::test]
10926async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10927    init_test(cx, |settings| {
10928        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10929    });
10930
10931    let mut cx = EditorLspTestContext::new_rust(
10932        lsp::ServerCapabilities {
10933            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10934            ..Default::default()
10935        },
10936        cx,
10937    )
10938    .await;
10939
10940    // Set up a buffer white some trailing whitespace and no trailing newline.
10941    cx.set_state(
10942        &[
10943            "one ",   //
10944            "twoˇ",   //
10945            "three ", //
10946            "four",   //
10947        ]
10948        .join("\n"),
10949    );
10950
10951    // Submit a format request.
10952    let format = cx
10953        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10954        .unwrap();
10955
10956    // Record which buffer changes have been sent to the language server
10957    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10958    cx.lsp
10959        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10960            let buffer_changes = buffer_changes.clone();
10961            move |params, _| {
10962                buffer_changes.lock().extend(
10963                    params
10964                        .content_changes
10965                        .into_iter()
10966                        .map(|e| (e.range.unwrap(), e.text)),
10967                );
10968            }
10969        });
10970
10971    // Handle formatting requests to the language server.
10972    cx.lsp
10973        .set_request_handler::<lsp::request::Formatting, _, _>({
10974            let buffer_changes = buffer_changes.clone();
10975            move |_, _| {
10976                // When formatting is requested, trailing whitespace has already been stripped,
10977                // and the trailing newline has already been added.
10978                assert_eq!(
10979                    &buffer_changes.lock()[1..],
10980                    &[
10981                        (
10982                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10983                            "".into()
10984                        ),
10985                        (
10986                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10987                            "".into()
10988                        ),
10989                        (
10990                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10991                            "\n".into()
10992                        ),
10993                    ]
10994                );
10995
10996                // Insert blank lines between each line of the buffer.
10997                async move {
10998                    Ok(Some(vec![
10999                        lsp::TextEdit {
11000                            range: lsp::Range::new(
11001                                lsp::Position::new(1, 0),
11002                                lsp::Position::new(1, 0),
11003                            ),
11004                            new_text: "\n".into(),
11005                        },
11006                        lsp::TextEdit {
11007                            range: lsp::Range::new(
11008                                lsp::Position::new(2, 0),
11009                                lsp::Position::new(2, 0),
11010                            ),
11011                            new_text: "\n".into(),
11012                        },
11013                    ]))
11014                }
11015            }
11016        });
11017
11018    // After formatting the buffer, the trailing whitespace is stripped,
11019    // a newline is appended, and the edits provided by the language server
11020    // have been applied.
11021    format.await.unwrap();
11022    cx.assert_editor_state(
11023        &[
11024            "one",   //
11025            "",      //
11026            "twoˇ",  //
11027            "",      //
11028            "three", //
11029            "four",  //
11030            "",      //
11031        ]
11032        .join("\n"),
11033    );
11034
11035    // Undoing the formatting undoes the trailing whitespace removal, the
11036    // trailing newline, and the LSP edits.
11037    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11038    cx.assert_editor_state(
11039        &[
11040            "one ",   //
11041            "twoˇ",   //
11042            "three ", //
11043            "four",   //
11044        ]
11045        .join("\n"),
11046    );
11047}
11048
11049#[gpui::test]
11050async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11051    cx: &mut TestAppContext,
11052) {
11053    init_test(cx, |_| {});
11054
11055    cx.update(|cx| {
11056        cx.update_global::<SettingsStore, _>(|settings, cx| {
11057            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11058                settings.auto_signature_help = Some(true);
11059            });
11060        });
11061    });
11062
11063    let mut cx = EditorLspTestContext::new_rust(
11064        lsp::ServerCapabilities {
11065            signature_help_provider: Some(lsp::SignatureHelpOptions {
11066                ..Default::default()
11067            }),
11068            ..Default::default()
11069        },
11070        cx,
11071    )
11072    .await;
11073
11074    let language = Language::new(
11075        LanguageConfig {
11076            name: "Rust".into(),
11077            brackets: BracketPairConfig {
11078                pairs: vec![
11079                    BracketPair {
11080                        start: "{".to_string(),
11081                        end: "}".to_string(),
11082                        close: true,
11083                        surround: true,
11084                        newline: true,
11085                    },
11086                    BracketPair {
11087                        start: "(".to_string(),
11088                        end: ")".to_string(),
11089                        close: true,
11090                        surround: true,
11091                        newline: true,
11092                    },
11093                    BracketPair {
11094                        start: "/*".to_string(),
11095                        end: " */".to_string(),
11096                        close: true,
11097                        surround: true,
11098                        newline: true,
11099                    },
11100                    BracketPair {
11101                        start: "[".to_string(),
11102                        end: "]".to_string(),
11103                        close: false,
11104                        surround: false,
11105                        newline: true,
11106                    },
11107                    BracketPair {
11108                        start: "\"".to_string(),
11109                        end: "\"".to_string(),
11110                        close: true,
11111                        surround: true,
11112                        newline: false,
11113                    },
11114                    BracketPair {
11115                        start: "<".to_string(),
11116                        end: ">".to_string(),
11117                        close: false,
11118                        surround: true,
11119                        newline: true,
11120                    },
11121                ],
11122                ..Default::default()
11123            },
11124            autoclose_before: "})]".to_string(),
11125            ..Default::default()
11126        },
11127        Some(tree_sitter_rust::LANGUAGE.into()),
11128    );
11129    let language = Arc::new(language);
11130
11131    cx.language_registry().add(language.clone());
11132    cx.update_buffer(|buffer, cx| {
11133        buffer.set_language(Some(language), cx);
11134    });
11135
11136    cx.set_state(
11137        &r#"
11138            fn main() {
11139                sampleˇ
11140            }
11141        "#
11142        .unindent(),
11143    );
11144
11145    cx.update_editor(|editor, window, cx| {
11146        editor.handle_input("(", window, cx);
11147    });
11148    cx.assert_editor_state(
11149        &"
11150            fn main() {
11151                sample(ˇ)
11152            }
11153        "
11154        .unindent(),
11155    );
11156
11157    let mocked_response = lsp::SignatureHelp {
11158        signatures: vec![lsp::SignatureInformation {
11159            label: "fn sample(param1: u8, param2: u8)".to_string(),
11160            documentation: None,
11161            parameters: Some(vec![
11162                lsp::ParameterInformation {
11163                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11164                    documentation: None,
11165                },
11166                lsp::ParameterInformation {
11167                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11168                    documentation: None,
11169                },
11170            ]),
11171            active_parameter: None,
11172        }],
11173        active_signature: Some(0),
11174        active_parameter: Some(0),
11175    };
11176    handle_signature_help_request(&mut cx, mocked_response).await;
11177
11178    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11179        .await;
11180
11181    cx.editor(|editor, _, _| {
11182        let signature_help_state = editor.signature_help_state.popover().cloned();
11183        let signature = signature_help_state.unwrap();
11184        assert_eq!(
11185            signature.signatures[signature.current_signature].label,
11186            "fn sample(param1: u8, param2: u8)"
11187        );
11188    });
11189}
11190
11191#[gpui::test]
11192async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11193    init_test(cx, |_| {});
11194
11195    cx.update(|cx| {
11196        cx.update_global::<SettingsStore, _>(|settings, cx| {
11197            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11198                settings.auto_signature_help = Some(false);
11199                settings.show_signature_help_after_edits = Some(false);
11200            });
11201        });
11202    });
11203
11204    let mut cx = EditorLspTestContext::new_rust(
11205        lsp::ServerCapabilities {
11206            signature_help_provider: Some(lsp::SignatureHelpOptions {
11207                ..Default::default()
11208            }),
11209            ..Default::default()
11210        },
11211        cx,
11212    )
11213    .await;
11214
11215    let language = Language::new(
11216        LanguageConfig {
11217            name: "Rust".into(),
11218            brackets: BracketPairConfig {
11219                pairs: vec![
11220                    BracketPair {
11221                        start: "{".to_string(),
11222                        end: "}".to_string(),
11223                        close: true,
11224                        surround: true,
11225                        newline: true,
11226                    },
11227                    BracketPair {
11228                        start: "(".to_string(),
11229                        end: ")".to_string(),
11230                        close: true,
11231                        surround: true,
11232                        newline: true,
11233                    },
11234                    BracketPair {
11235                        start: "/*".to_string(),
11236                        end: " */".to_string(),
11237                        close: true,
11238                        surround: true,
11239                        newline: true,
11240                    },
11241                    BracketPair {
11242                        start: "[".to_string(),
11243                        end: "]".to_string(),
11244                        close: false,
11245                        surround: false,
11246                        newline: true,
11247                    },
11248                    BracketPair {
11249                        start: "\"".to_string(),
11250                        end: "\"".to_string(),
11251                        close: true,
11252                        surround: true,
11253                        newline: false,
11254                    },
11255                    BracketPair {
11256                        start: "<".to_string(),
11257                        end: ">".to_string(),
11258                        close: false,
11259                        surround: true,
11260                        newline: true,
11261                    },
11262                ],
11263                ..Default::default()
11264            },
11265            autoclose_before: "})]".to_string(),
11266            ..Default::default()
11267        },
11268        Some(tree_sitter_rust::LANGUAGE.into()),
11269    );
11270    let language = Arc::new(language);
11271
11272    cx.language_registry().add(language.clone());
11273    cx.update_buffer(|buffer, cx| {
11274        buffer.set_language(Some(language), cx);
11275    });
11276
11277    // Ensure that signature_help is not called when no signature help is enabled.
11278    cx.set_state(
11279        &r#"
11280            fn main() {
11281                sampleˇ
11282            }
11283        "#
11284        .unindent(),
11285    );
11286    cx.update_editor(|editor, window, cx| {
11287        editor.handle_input("(", window, cx);
11288    });
11289    cx.assert_editor_state(
11290        &"
11291            fn main() {
11292                sample(ˇ)
11293            }
11294        "
11295        .unindent(),
11296    );
11297    cx.editor(|editor, _, _| {
11298        assert!(editor.signature_help_state.task().is_none());
11299    });
11300
11301    let mocked_response = lsp::SignatureHelp {
11302        signatures: vec![lsp::SignatureInformation {
11303            label: "fn sample(param1: u8, param2: u8)".to_string(),
11304            documentation: None,
11305            parameters: Some(vec![
11306                lsp::ParameterInformation {
11307                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11308                    documentation: None,
11309                },
11310                lsp::ParameterInformation {
11311                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11312                    documentation: None,
11313                },
11314            ]),
11315            active_parameter: None,
11316        }],
11317        active_signature: Some(0),
11318        active_parameter: Some(0),
11319    };
11320
11321    // Ensure that signature_help is called when enabled afte edits
11322    cx.update(|_, cx| {
11323        cx.update_global::<SettingsStore, _>(|settings, cx| {
11324            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11325                settings.auto_signature_help = Some(false);
11326                settings.show_signature_help_after_edits = Some(true);
11327            });
11328        });
11329    });
11330    cx.set_state(
11331        &r#"
11332            fn main() {
11333                sampleˇ
11334            }
11335        "#
11336        .unindent(),
11337    );
11338    cx.update_editor(|editor, window, cx| {
11339        editor.handle_input("(", window, cx);
11340    });
11341    cx.assert_editor_state(
11342        &"
11343            fn main() {
11344                sample(ˇ)
11345            }
11346        "
11347        .unindent(),
11348    );
11349    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11350    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11351        .await;
11352    cx.update_editor(|editor, _, _| {
11353        let signature_help_state = editor.signature_help_state.popover().cloned();
11354        assert!(signature_help_state.is_some());
11355        let signature = signature_help_state.unwrap();
11356        assert_eq!(
11357            signature.signatures[signature.current_signature].label,
11358            "fn sample(param1: u8, param2: u8)"
11359        );
11360        editor.signature_help_state = SignatureHelpState::default();
11361    });
11362
11363    // Ensure that signature_help is called when auto signature help override is enabled
11364    cx.update(|_, cx| {
11365        cx.update_global::<SettingsStore, _>(|settings, cx| {
11366            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11367                settings.auto_signature_help = Some(true);
11368                settings.show_signature_help_after_edits = Some(false);
11369            });
11370        });
11371    });
11372    cx.set_state(
11373        &r#"
11374            fn main() {
11375                sampleˇ
11376            }
11377        "#
11378        .unindent(),
11379    );
11380    cx.update_editor(|editor, window, cx| {
11381        editor.handle_input("(", window, cx);
11382    });
11383    cx.assert_editor_state(
11384        &"
11385            fn main() {
11386                sample(ˇ)
11387            }
11388        "
11389        .unindent(),
11390    );
11391    handle_signature_help_request(&mut cx, mocked_response).await;
11392    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11393        .await;
11394    cx.editor(|editor, _, _| {
11395        let signature_help_state = editor.signature_help_state.popover().cloned();
11396        assert!(signature_help_state.is_some());
11397        let signature = signature_help_state.unwrap();
11398        assert_eq!(
11399            signature.signatures[signature.current_signature].label,
11400            "fn sample(param1: u8, param2: u8)"
11401        );
11402    });
11403}
11404
11405#[gpui::test]
11406async fn test_signature_help(cx: &mut TestAppContext) {
11407    init_test(cx, |_| {});
11408    cx.update(|cx| {
11409        cx.update_global::<SettingsStore, _>(|settings, cx| {
11410            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11411                settings.auto_signature_help = Some(true);
11412            });
11413        });
11414    });
11415
11416    let mut cx = EditorLspTestContext::new_rust(
11417        lsp::ServerCapabilities {
11418            signature_help_provider: Some(lsp::SignatureHelpOptions {
11419                ..Default::default()
11420            }),
11421            ..Default::default()
11422        },
11423        cx,
11424    )
11425    .await;
11426
11427    // A test that directly calls `show_signature_help`
11428    cx.update_editor(|editor, window, cx| {
11429        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11430    });
11431
11432    let mocked_response = lsp::SignatureHelp {
11433        signatures: vec![lsp::SignatureInformation {
11434            label: "fn sample(param1: u8, param2: u8)".to_string(),
11435            documentation: None,
11436            parameters: Some(vec![
11437                lsp::ParameterInformation {
11438                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11439                    documentation: None,
11440                },
11441                lsp::ParameterInformation {
11442                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11443                    documentation: None,
11444                },
11445            ]),
11446            active_parameter: None,
11447        }],
11448        active_signature: Some(0),
11449        active_parameter: Some(0),
11450    };
11451    handle_signature_help_request(&mut cx, mocked_response).await;
11452
11453    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11454        .await;
11455
11456    cx.editor(|editor, _, _| {
11457        let signature_help_state = editor.signature_help_state.popover().cloned();
11458        assert!(signature_help_state.is_some());
11459        let signature = signature_help_state.unwrap();
11460        assert_eq!(
11461            signature.signatures[signature.current_signature].label,
11462            "fn sample(param1: u8, param2: u8)"
11463        );
11464    });
11465
11466    // When exiting outside from inside the brackets, `signature_help` is closed.
11467    cx.set_state(indoc! {"
11468        fn main() {
11469            sample(ˇ);
11470        }
11471
11472        fn sample(param1: u8, param2: u8) {}
11473    "});
11474
11475    cx.update_editor(|editor, window, cx| {
11476        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11477            s.select_ranges([0..0])
11478        });
11479    });
11480
11481    let mocked_response = lsp::SignatureHelp {
11482        signatures: Vec::new(),
11483        active_signature: None,
11484        active_parameter: None,
11485    };
11486    handle_signature_help_request(&mut cx, mocked_response).await;
11487
11488    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11489        .await;
11490
11491    cx.editor(|editor, _, _| {
11492        assert!(!editor.signature_help_state.is_shown());
11493    });
11494
11495    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11496    cx.set_state(indoc! {"
11497        fn main() {
11498            sample(ˇ);
11499        }
11500
11501        fn sample(param1: u8, param2: u8) {}
11502    "});
11503
11504    let mocked_response = lsp::SignatureHelp {
11505        signatures: vec![lsp::SignatureInformation {
11506            label: "fn sample(param1: u8, param2: u8)".to_string(),
11507            documentation: None,
11508            parameters: Some(vec![
11509                lsp::ParameterInformation {
11510                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11511                    documentation: None,
11512                },
11513                lsp::ParameterInformation {
11514                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11515                    documentation: None,
11516                },
11517            ]),
11518            active_parameter: None,
11519        }],
11520        active_signature: Some(0),
11521        active_parameter: Some(0),
11522    };
11523    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11524    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11525        .await;
11526    cx.editor(|editor, _, _| {
11527        assert!(editor.signature_help_state.is_shown());
11528    });
11529
11530    // Restore the popover with more parameter input
11531    cx.set_state(indoc! {"
11532        fn main() {
11533            sample(param1, param2ˇ);
11534        }
11535
11536        fn sample(param1: u8, param2: u8) {}
11537    "});
11538
11539    let mocked_response = lsp::SignatureHelp {
11540        signatures: vec![lsp::SignatureInformation {
11541            label: "fn sample(param1: u8, param2: u8)".to_string(),
11542            documentation: None,
11543            parameters: Some(vec![
11544                lsp::ParameterInformation {
11545                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11546                    documentation: None,
11547                },
11548                lsp::ParameterInformation {
11549                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11550                    documentation: None,
11551                },
11552            ]),
11553            active_parameter: None,
11554        }],
11555        active_signature: Some(0),
11556        active_parameter: Some(1),
11557    };
11558    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11559    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11560        .await;
11561
11562    // When selecting a range, the popover is gone.
11563    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11564    cx.update_editor(|editor, window, cx| {
11565        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11566            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11567        })
11568    });
11569    cx.assert_editor_state(indoc! {"
11570        fn main() {
11571            sample(param1, «ˇparam2»);
11572        }
11573
11574        fn sample(param1: u8, param2: u8) {}
11575    "});
11576    cx.editor(|editor, _, _| {
11577        assert!(!editor.signature_help_state.is_shown());
11578    });
11579
11580    // When unselecting again, the popover is back if within the brackets.
11581    cx.update_editor(|editor, window, cx| {
11582        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11583            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11584        })
11585    });
11586    cx.assert_editor_state(indoc! {"
11587        fn main() {
11588            sample(param1, ˇparam2);
11589        }
11590
11591        fn sample(param1: u8, param2: u8) {}
11592    "});
11593    handle_signature_help_request(&mut cx, mocked_response).await;
11594    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11595        .await;
11596    cx.editor(|editor, _, _| {
11597        assert!(editor.signature_help_state.is_shown());
11598    });
11599
11600    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11601    cx.update_editor(|editor, window, cx| {
11602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11603            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11604            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11605        })
11606    });
11607    cx.assert_editor_state(indoc! {"
11608        fn main() {
11609            sample(param1, ˇparam2);
11610        }
11611
11612        fn sample(param1: u8, param2: u8) {}
11613    "});
11614
11615    let mocked_response = lsp::SignatureHelp {
11616        signatures: vec![lsp::SignatureInformation {
11617            label: "fn sample(param1: u8, param2: u8)".to_string(),
11618            documentation: None,
11619            parameters: Some(vec![
11620                lsp::ParameterInformation {
11621                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11622                    documentation: None,
11623                },
11624                lsp::ParameterInformation {
11625                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11626                    documentation: None,
11627                },
11628            ]),
11629            active_parameter: None,
11630        }],
11631        active_signature: Some(0),
11632        active_parameter: Some(1),
11633    };
11634    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11635    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11636        .await;
11637    cx.update_editor(|editor, _, cx| {
11638        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11639    });
11640    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11641        .await;
11642    cx.update_editor(|editor, window, cx| {
11643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11644            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11645        })
11646    });
11647    cx.assert_editor_state(indoc! {"
11648        fn main() {
11649            sample(param1, «ˇparam2»);
11650        }
11651
11652        fn sample(param1: u8, param2: u8) {}
11653    "});
11654    cx.update_editor(|editor, window, cx| {
11655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11656            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11657        })
11658    });
11659    cx.assert_editor_state(indoc! {"
11660        fn main() {
11661            sample(param1, ˇparam2);
11662        }
11663
11664        fn sample(param1: u8, param2: u8) {}
11665    "});
11666    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11667        .await;
11668}
11669
11670#[gpui::test]
11671async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11672    init_test(cx, |_| {});
11673
11674    let mut cx = EditorLspTestContext::new_rust(
11675        lsp::ServerCapabilities {
11676            signature_help_provider: Some(lsp::SignatureHelpOptions {
11677                ..Default::default()
11678            }),
11679            ..Default::default()
11680        },
11681        cx,
11682    )
11683    .await;
11684
11685    cx.set_state(indoc! {"
11686        fn main() {
11687            overloadedˇ
11688        }
11689    "});
11690
11691    cx.update_editor(|editor, window, cx| {
11692        editor.handle_input("(", window, cx);
11693        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11694    });
11695
11696    // Mock response with 3 signatures
11697    let mocked_response = lsp::SignatureHelp {
11698        signatures: vec![
11699            lsp::SignatureInformation {
11700                label: "fn overloaded(x: i32)".to_string(),
11701                documentation: None,
11702                parameters: Some(vec![lsp::ParameterInformation {
11703                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11704                    documentation: None,
11705                }]),
11706                active_parameter: None,
11707            },
11708            lsp::SignatureInformation {
11709                label: "fn overloaded(x: i32, y: i32)".to_string(),
11710                documentation: None,
11711                parameters: Some(vec![
11712                    lsp::ParameterInformation {
11713                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11714                        documentation: None,
11715                    },
11716                    lsp::ParameterInformation {
11717                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11718                        documentation: None,
11719                    },
11720                ]),
11721                active_parameter: None,
11722            },
11723            lsp::SignatureInformation {
11724                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11725                documentation: None,
11726                parameters: Some(vec![
11727                    lsp::ParameterInformation {
11728                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11729                        documentation: None,
11730                    },
11731                    lsp::ParameterInformation {
11732                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11733                        documentation: None,
11734                    },
11735                    lsp::ParameterInformation {
11736                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11737                        documentation: None,
11738                    },
11739                ]),
11740                active_parameter: None,
11741            },
11742        ],
11743        active_signature: Some(1),
11744        active_parameter: Some(0),
11745    };
11746    handle_signature_help_request(&mut cx, mocked_response).await;
11747
11748    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11749        .await;
11750
11751    // Verify we have multiple signatures and the right one is selected
11752    cx.editor(|editor, _, _| {
11753        let popover = editor.signature_help_state.popover().cloned().unwrap();
11754        assert_eq!(popover.signatures.len(), 3);
11755        // active_signature was 1, so that should be the current
11756        assert_eq!(popover.current_signature, 1);
11757        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11758        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11759        assert_eq!(
11760            popover.signatures[2].label,
11761            "fn overloaded(x: i32, y: i32, z: i32)"
11762        );
11763    });
11764
11765    // Test navigation functionality
11766    cx.update_editor(|editor, window, cx| {
11767        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11768    });
11769
11770    cx.editor(|editor, _, _| {
11771        let popover = editor.signature_help_state.popover().cloned().unwrap();
11772        assert_eq!(popover.current_signature, 2);
11773    });
11774
11775    // Test wrap around
11776    cx.update_editor(|editor, window, cx| {
11777        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11778    });
11779
11780    cx.editor(|editor, _, _| {
11781        let popover = editor.signature_help_state.popover().cloned().unwrap();
11782        assert_eq!(popover.current_signature, 0);
11783    });
11784
11785    // Test previous navigation
11786    cx.update_editor(|editor, window, cx| {
11787        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11788    });
11789
11790    cx.editor(|editor, _, _| {
11791        let popover = editor.signature_help_state.popover().cloned().unwrap();
11792        assert_eq!(popover.current_signature, 2);
11793    });
11794}
11795
11796#[gpui::test]
11797async fn test_completion_mode(cx: &mut TestAppContext) {
11798    init_test(cx, |_| {});
11799    let mut cx = EditorLspTestContext::new_rust(
11800        lsp::ServerCapabilities {
11801            completion_provider: Some(lsp::CompletionOptions {
11802                resolve_provider: Some(true),
11803                ..Default::default()
11804            }),
11805            ..Default::default()
11806        },
11807        cx,
11808    )
11809    .await;
11810
11811    struct Run {
11812        run_description: &'static str,
11813        initial_state: String,
11814        buffer_marked_text: String,
11815        completion_label: &'static str,
11816        completion_text: &'static str,
11817        expected_with_insert_mode: String,
11818        expected_with_replace_mode: String,
11819        expected_with_replace_subsequence_mode: String,
11820        expected_with_replace_suffix_mode: String,
11821    }
11822
11823    let runs = [
11824        Run {
11825            run_description: "Start of word matches completion text",
11826            initial_state: "before ediˇ after".into(),
11827            buffer_marked_text: "before <edi|> after".into(),
11828            completion_label: "editor",
11829            completion_text: "editor",
11830            expected_with_insert_mode: "before editorˇ after".into(),
11831            expected_with_replace_mode: "before editorˇ after".into(),
11832            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11833            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11834        },
11835        Run {
11836            run_description: "Accept same text at the middle of the word",
11837            initial_state: "before ediˇtor after".into(),
11838            buffer_marked_text: "before <edi|tor> after".into(),
11839            completion_label: "editor",
11840            completion_text: "editor",
11841            expected_with_insert_mode: "before editorˇtor after".into(),
11842            expected_with_replace_mode: "before editorˇ after".into(),
11843            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11844            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11845        },
11846        Run {
11847            run_description: "End of word matches completion text -- cursor at end",
11848            initial_state: "before torˇ after".into(),
11849            buffer_marked_text: "before <tor|> after".into(),
11850            completion_label: "editor",
11851            completion_text: "editor",
11852            expected_with_insert_mode: "before editorˇ after".into(),
11853            expected_with_replace_mode: "before editorˇ after".into(),
11854            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11855            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11856        },
11857        Run {
11858            run_description: "End of word matches completion text -- cursor at start",
11859            initial_state: "before ˇtor after".into(),
11860            buffer_marked_text: "before <|tor> after".into(),
11861            completion_label: "editor",
11862            completion_text: "editor",
11863            expected_with_insert_mode: "before editorˇtor after".into(),
11864            expected_with_replace_mode: "before editorˇ after".into(),
11865            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11866            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11867        },
11868        Run {
11869            run_description: "Prepend text containing whitespace",
11870            initial_state: "pˇfield: bool".into(),
11871            buffer_marked_text: "<p|field>: bool".into(),
11872            completion_label: "pub ",
11873            completion_text: "pub ",
11874            expected_with_insert_mode: "pub ˇfield: bool".into(),
11875            expected_with_replace_mode: "pub ˇ: bool".into(),
11876            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11877            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11878        },
11879        Run {
11880            run_description: "Add element to start of list",
11881            initial_state: "[element_ˇelement_2]".into(),
11882            buffer_marked_text: "[<element_|element_2>]".into(),
11883            completion_label: "element_1",
11884            completion_text: "element_1",
11885            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11886            expected_with_replace_mode: "[element_1ˇ]".into(),
11887            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11888            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11889        },
11890        Run {
11891            run_description: "Add element to start of list -- first and second elements are equal",
11892            initial_state: "[elˇelement]".into(),
11893            buffer_marked_text: "[<el|element>]".into(),
11894            completion_label: "element",
11895            completion_text: "element",
11896            expected_with_insert_mode: "[elementˇelement]".into(),
11897            expected_with_replace_mode: "[elementˇ]".into(),
11898            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11899            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11900        },
11901        Run {
11902            run_description: "Ends with matching suffix",
11903            initial_state: "SubˇError".into(),
11904            buffer_marked_text: "<Sub|Error>".into(),
11905            completion_label: "SubscriptionError",
11906            completion_text: "SubscriptionError",
11907            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11908            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11909            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11910            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11911        },
11912        Run {
11913            run_description: "Suffix is a subsequence -- contiguous",
11914            initial_state: "SubˇErr".into(),
11915            buffer_marked_text: "<Sub|Err>".into(),
11916            completion_label: "SubscriptionError",
11917            completion_text: "SubscriptionError",
11918            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11919            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11920            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11921            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11922        },
11923        Run {
11924            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11925            initial_state: "Suˇscrirr".into(),
11926            buffer_marked_text: "<Su|scrirr>".into(),
11927            completion_label: "SubscriptionError",
11928            completion_text: "SubscriptionError",
11929            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11930            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11931            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11932            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11933        },
11934        Run {
11935            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11936            initial_state: "foo(indˇix)".into(),
11937            buffer_marked_text: "foo(<ind|ix>)".into(),
11938            completion_label: "node_index",
11939            completion_text: "node_index",
11940            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11941            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11942            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11943            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11944        },
11945        Run {
11946            run_description: "Replace range ends before cursor - should extend to cursor",
11947            initial_state: "before editˇo after".into(),
11948            buffer_marked_text: "before <{ed}>it|o after".into(),
11949            completion_label: "editor",
11950            completion_text: "editor",
11951            expected_with_insert_mode: "before editorˇo after".into(),
11952            expected_with_replace_mode: "before editorˇo after".into(),
11953            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11954            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11955        },
11956        Run {
11957            run_description: "Uses label for suffix matching",
11958            initial_state: "before ediˇtor after".into(),
11959            buffer_marked_text: "before <edi|tor> after".into(),
11960            completion_label: "editor",
11961            completion_text: "editor()",
11962            expected_with_insert_mode: "before editor()ˇtor after".into(),
11963            expected_with_replace_mode: "before editor()ˇ after".into(),
11964            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11965            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11966        },
11967        Run {
11968            run_description: "Case insensitive subsequence and suffix matching",
11969            initial_state: "before EDiˇtoR after".into(),
11970            buffer_marked_text: "before <EDi|toR> after".into(),
11971            completion_label: "editor",
11972            completion_text: "editor",
11973            expected_with_insert_mode: "before editorˇtoR after".into(),
11974            expected_with_replace_mode: "before editorˇ after".into(),
11975            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11976            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11977        },
11978    ];
11979
11980    for run in runs {
11981        let run_variations = [
11982            (LspInsertMode::Insert, run.expected_with_insert_mode),
11983            (LspInsertMode::Replace, run.expected_with_replace_mode),
11984            (
11985                LspInsertMode::ReplaceSubsequence,
11986                run.expected_with_replace_subsequence_mode,
11987            ),
11988            (
11989                LspInsertMode::ReplaceSuffix,
11990                run.expected_with_replace_suffix_mode,
11991            ),
11992        ];
11993
11994        for (lsp_insert_mode, expected_text) in run_variations {
11995            eprintln!(
11996                "run = {:?}, mode = {lsp_insert_mode:.?}",
11997                run.run_description,
11998            );
11999
12000            update_test_language_settings(&mut cx, |settings| {
12001                settings.defaults.completions = Some(CompletionSettings {
12002                    lsp_insert_mode,
12003                    words: WordsCompletionMode::Disabled,
12004                    lsp: true,
12005                    lsp_fetch_timeout_ms: 0,
12006                });
12007            });
12008
12009            cx.set_state(&run.initial_state);
12010            cx.update_editor(|editor, window, cx| {
12011                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12012            });
12013
12014            let counter = Arc::new(AtomicUsize::new(0));
12015            handle_completion_request_with_insert_and_replace(
12016                &mut cx,
12017                &run.buffer_marked_text,
12018                vec![(run.completion_label, run.completion_text)],
12019                counter.clone(),
12020            )
12021            .await;
12022            cx.condition(|editor, _| editor.context_menu_visible())
12023                .await;
12024            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12025
12026            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12027                editor
12028                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12029                    .unwrap()
12030            });
12031            cx.assert_editor_state(&expected_text);
12032            handle_resolve_completion_request(&mut cx, None).await;
12033            apply_additional_edits.await.unwrap();
12034        }
12035    }
12036}
12037
12038#[gpui::test]
12039async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12040    init_test(cx, |_| {});
12041    let mut cx = EditorLspTestContext::new_rust(
12042        lsp::ServerCapabilities {
12043            completion_provider: Some(lsp::CompletionOptions {
12044                resolve_provider: Some(true),
12045                ..Default::default()
12046            }),
12047            ..Default::default()
12048        },
12049        cx,
12050    )
12051    .await;
12052
12053    let initial_state = "SubˇError";
12054    let buffer_marked_text = "<Sub|Error>";
12055    let completion_text = "SubscriptionError";
12056    let expected_with_insert_mode = "SubscriptionErrorˇError";
12057    let expected_with_replace_mode = "SubscriptionErrorˇ";
12058
12059    update_test_language_settings(&mut cx, |settings| {
12060        settings.defaults.completions = Some(CompletionSettings {
12061            words: WordsCompletionMode::Disabled,
12062            // set the opposite here to ensure that the action is overriding the default behavior
12063            lsp_insert_mode: LspInsertMode::Insert,
12064            lsp: true,
12065            lsp_fetch_timeout_ms: 0,
12066        });
12067    });
12068
12069    cx.set_state(initial_state);
12070    cx.update_editor(|editor, window, cx| {
12071        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12072    });
12073
12074    let counter = Arc::new(AtomicUsize::new(0));
12075    handle_completion_request_with_insert_and_replace(
12076        &mut cx,
12077        &buffer_marked_text,
12078        vec![(completion_text, completion_text)],
12079        counter.clone(),
12080    )
12081    .await;
12082    cx.condition(|editor, _| editor.context_menu_visible())
12083        .await;
12084    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12085
12086    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12087        editor
12088            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12089            .unwrap()
12090    });
12091    cx.assert_editor_state(&expected_with_replace_mode);
12092    handle_resolve_completion_request(&mut cx, None).await;
12093    apply_additional_edits.await.unwrap();
12094
12095    update_test_language_settings(&mut cx, |settings| {
12096        settings.defaults.completions = Some(CompletionSettings {
12097            words: WordsCompletionMode::Disabled,
12098            // set the opposite here to ensure that the action is overriding the default behavior
12099            lsp_insert_mode: LspInsertMode::Replace,
12100            lsp: true,
12101            lsp_fetch_timeout_ms: 0,
12102        });
12103    });
12104
12105    cx.set_state(initial_state);
12106    cx.update_editor(|editor, window, cx| {
12107        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12108    });
12109    handle_completion_request_with_insert_and_replace(
12110        &mut cx,
12111        &buffer_marked_text,
12112        vec![(completion_text, completion_text)],
12113        counter.clone(),
12114    )
12115    .await;
12116    cx.condition(|editor, _| editor.context_menu_visible())
12117        .await;
12118    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12119
12120    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12121        editor
12122            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12123            .unwrap()
12124    });
12125    cx.assert_editor_state(&expected_with_insert_mode);
12126    handle_resolve_completion_request(&mut cx, None).await;
12127    apply_additional_edits.await.unwrap();
12128}
12129
12130#[gpui::test]
12131async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12132    init_test(cx, |_| {});
12133    let mut cx = EditorLspTestContext::new_rust(
12134        lsp::ServerCapabilities {
12135            completion_provider: Some(lsp::CompletionOptions {
12136                resolve_provider: Some(true),
12137                ..Default::default()
12138            }),
12139            ..Default::default()
12140        },
12141        cx,
12142    )
12143    .await;
12144
12145    // scenario: surrounding text matches completion text
12146    let completion_text = "to_offset";
12147    let initial_state = indoc! {"
12148        1. buf.to_offˇsuffix
12149        2. buf.to_offˇsuf
12150        3. buf.to_offˇfix
12151        4. buf.to_offˇ
12152        5. into_offˇensive
12153        6. ˇsuffix
12154        7. let ˇ //
12155        8. aaˇzz
12156        9. buf.to_off«zzzzzˇ»suffix
12157        10. buf.«ˇzzzzz»suffix
12158        11. to_off«ˇzzzzz»
12159
12160        buf.to_offˇsuffix  // newest cursor
12161    "};
12162    let completion_marked_buffer = indoc! {"
12163        1. buf.to_offsuffix
12164        2. buf.to_offsuf
12165        3. buf.to_offfix
12166        4. buf.to_off
12167        5. into_offensive
12168        6. suffix
12169        7. let  //
12170        8. aazz
12171        9. buf.to_offzzzzzsuffix
12172        10. buf.zzzzzsuffix
12173        11. to_offzzzzz
12174
12175        buf.<to_off|suffix>  // newest cursor
12176    "};
12177    let expected = indoc! {"
12178        1. buf.to_offsetˇ
12179        2. buf.to_offsetˇsuf
12180        3. buf.to_offsetˇfix
12181        4. buf.to_offsetˇ
12182        5. into_offsetˇensive
12183        6. to_offsetˇsuffix
12184        7. let to_offsetˇ //
12185        8. aato_offsetˇzz
12186        9. buf.to_offsetˇ
12187        10. buf.to_offsetˇsuffix
12188        11. to_offsetˇ
12189
12190        buf.to_offsetˇ  // newest cursor
12191    "};
12192    cx.set_state(initial_state);
12193    cx.update_editor(|editor, window, cx| {
12194        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12195    });
12196    handle_completion_request_with_insert_and_replace(
12197        &mut cx,
12198        completion_marked_buffer,
12199        vec![(completion_text, completion_text)],
12200        Arc::new(AtomicUsize::new(0)),
12201    )
12202    .await;
12203    cx.condition(|editor, _| editor.context_menu_visible())
12204        .await;
12205    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12206        editor
12207            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12208            .unwrap()
12209    });
12210    cx.assert_editor_state(expected);
12211    handle_resolve_completion_request(&mut cx, None).await;
12212    apply_additional_edits.await.unwrap();
12213
12214    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12215    let completion_text = "foo_and_bar";
12216    let initial_state = indoc! {"
12217        1. ooanbˇ
12218        2. zooanbˇ
12219        3. ooanbˇz
12220        4. zooanbˇz
12221        5. ooanˇ
12222        6. oanbˇ
12223
12224        ooanbˇ
12225    "};
12226    let completion_marked_buffer = indoc! {"
12227        1. ooanb
12228        2. zooanb
12229        3. ooanbz
12230        4. zooanbz
12231        5. ooan
12232        6. oanb
12233
12234        <ooanb|>
12235    "};
12236    let expected = indoc! {"
12237        1. foo_and_barˇ
12238        2. zfoo_and_barˇ
12239        3. foo_and_barˇz
12240        4. zfoo_and_barˇz
12241        5. ooanfoo_and_barˇ
12242        6. oanbfoo_and_barˇ
12243
12244        foo_and_barˇ
12245    "};
12246    cx.set_state(initial_state);
12247    cx.update_editor(|editor, window, cx| {
12248        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12249    });
12250    handle_completion_request_with_insert_and_replace(
12251        &mut cx,
12252        completion_marked_buffer,
12253        vec![(completion_text, completion_text)],
12254        Arc::new(AtomicUsize::new(0)),
12255    )
12256    .await;
12257    cx.condition(|editor, _| editor.context_menu_visible())
12258        .await;
12259    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12260        editor
12261            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12262            .unwrap()
12263    });
12264    cx.assert_editor_state(expected);
12265    handle_resolve_completion_request(&mut cx, None).await;
12266    apply_additional_edits.await.unwrap();
12267
12268    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12269    // (expects the same as if it was inserted at the end)
12270    let completion_text = "foo_and_bar";
12271    let initial_state = indoc! {"
12272        1. ooˇanb
12273        2. zooˇanb
12274        3. ooˇanbz
12275        4. zooˇanbz
12276
12277        ooˇanb
12278    "};
12279    let completion_marked_buffer = indoc! {"
12280        1. ooanb
12281        2. zooanb
12282        3. ooanbz
12283        4. zooanbz
12284
12285        <oo|anb>
12286    "};
12287    let expected = indoc! {"
12288        1. foo_and_barˇ
12289        2. zfoo_and_barˇ
12290        3. foo_and_barˇz
12291        4. zfoo_and_barˇz
12292
12293        foo_and_barˇ
12294    "};
12295    cx.set_state(initial_state);
12296    cx.update_editor(|editor, window, cx| {
12297        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12298    });
12299    handle_completion_request_with_insert_and_replace(
12300        &mut cx,
12301        completion_marked_buffer,
12302        vec![(completion_text, completion_text)],
12303        Arc::new(AtomicUsize::new(0)),
12304    )
12305    .await;
12306    cx.condition(|editor, _| editor.context_menu_visible())
12307        .await;
12308    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12309        editor
12310            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12311            .unwrap()
12312    });
12313    cx.assert_editor_state(expected);
12314    handle_resolve_completion_request(&mut cx, None).await;
12315    apply_additional_edits.await.unwrap();
12316}
12317
12318// This used to crash
12319#[gpui::test]
12320async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12321    init_test(cx, |_| {});
12322
12323    let buffer_text = indoc! {"
12324        fn main() {
12325            10.satu;
12326
12327            //
12328            // separate cursors so they open in different excerpts (manually reproducible)
12329            //
12330
12331            10.satu20;
12332        }
12333    "};
12334    let multibuffer_text_with_selections = indoc! {"
12335        fn main() {
12336            10.satuˇ;
12337
12338            //
12339
12340            //
12341
12342            10.satuˇ20;
12343        }
12344    "};
12345    let expected_multibuffer = indoc! {"
12346        fn main() {
12347            10.saturating_sub()ˇ;
12348
12349            //
12350
12351            //
12352
12353            10.saturating_sub()ˇ;
12354        }
12355    "};
12356
12357    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12358    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12359
12360    let fs = FakeFs::new(cx.executor());
12361    fs.insert_tree(
12362        path!("/a"),
12363        json!({
12364            "main.rs": buffer_text,
12365        }),
12366    )
12367    .await;
12368
12369    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12370    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12371    language_registry.add(rust_lang());
12372    let mut fake_servers = language_registry.register_fake_lsp(
12373        "Rust",
12374        FakeLspAdapter {
12375            capabilities: lsp::ServerCapabilities {
12376                completion_provider: Some(lsp::CompletionOptions {
12377                    resolve_provider: None,
12378                    ..lsp::CompletionOptions::default()
12379                }),
12380                ..lsp::ServerCapabilities::default()
12381            },
12382            ..FakeLspAdapter::default()
12383        },
12384    );
12385    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12386    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12387    let buffer = project
12388        .update(cx, |project, cx| {
12389            project.open_local_buffer(path!("/a/main.rs"), cx)
12390        })
12391        .await
12392        .unwrap();
12393
12394    let multi_buffer = cx.new(|cx| {
12395        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12396        multi_buffer.push_excerpts(
12397            buffer.clone(),
12398            [ExcerptRange::new(0..first_excerpt_end)],
12399            cx,
12400        );
12401        multi_buffer.push_excerpts(
12402            buffer.clone(),
12403            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12404            cx,
12405        );
12406        multi_buffer
12407    });
12408
12409    let editor = workspace
12410        .update(cx, |_, window, cx| {
12411            cx.new(|cx| {
12412                Editor::new(
12413                    EditorMode::Full {
12414                        scale_ui_elements_with_buffer_font_size: false,
12415                        show_active_line_background: false,
12416                        sized_by_content: false,
12417                    },
12418                    multi_buffer.clone(),
12419                    Some(project.clone()),
12420                    window,
12421                    cx,
12422                )
12423            })
12424        })
12425        .unwrap();
12426
12427    let pane = workspace
12428        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12429        .unwrap();
12430    pane.update_in(cx, |pane, window, cx| {
12431        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12432    });
12433
12434    let fake_server = fake_servers.next().await.unwrap();
12435
12436    editor.update_in(cx, |editor, window, cx| {
12437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12438            s.select_ranges([
12439                Point::new(1, 11)..Point::new(1, 11),
12440                Point::new(7, 11)..Point::new(7, 11),
12441            ])
12442        });
12443
12444        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12445    });
12446
12447    editor.update_in(cx, |editor, window, cx| {
12448        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12449    });
12450
12451    fake_server
12452        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12453            let completion_item = lsp::CompletionItem {
12454                label: "saturating_sub()".into(),
12455                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12456                    lsp::InsertReplaceEdit {
12457                        new_text: "saturating_sub()".to_owned(),
12458                        insert: lsp::Range::new(
12459                            lsp::Position::new(7, 7),
12460                            lsp::Position::new(7, 11),
12461                        ),
12462                        replace: lsp::Range::new(
12463                            lsp::Position::new(7, 7),
12464                            lsp::Position::new(7, 13),
12465                        ),
12466                    },
12467                )),
12468                ..lsp::CompletionItem::default()
12469            };
12470
12471            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12472        })
12473        .next()
12474        .await
12475        .unwrap();
12476
12477    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12478        .await;
12479
12480    editor
12481        .update_in(cx, |editor, window, cx| {
12482            editor
12483                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12484                .unwrap()
12485        })
12486        .await
12487        .unwrap();
12488
12489    editor.update(cx, |editor, cx| {
12490        assert_text_with_selections(editor, expected_multibuffer, cx);
12491    })
12492}
12493
12494#[gpui::test]
12495async fn test_completion(cx: &mut TestAppContext) {
12496    init_test(cx, |_| {});
12497
12498    let mut cx = EditorLspTestContext::new_rust(
12499        lsp::ServerCapabilities {
12500            completion_provider: Some(lsp::CompletionOptions {
12501                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12502                resolve_provider: Some(true),
12503                ..Default::default()
12504            }),
12505            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12506            ..Default::default()
12507        },
12508        cx,
12509    )
12510    .await;
12511    let counter = Arc::new(AtomicUsize::new(0));
12512
12513    cx.set_state(indoc! {"
12514        oneˇ
12515        two
12516        three
12517    "});
12518    cx.simulate_keystroke(".");
12519    handle_completion_request(
12520        indoc! {"
12521            one.|<>
12522            two
12523            three
12524        "},
12525        vec!["first_completion", "second_completion"],
12526        true,
12527        counter.clone(),
12528        &mut cx,
12529    )
12530    .await;
12531    cx.condition(|editor, _| editor.context_menu_visible())
12532        .await;
12533    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12534
12535    let _handler = handle_signature_help_request(
12536        &mut cx,
12537        lsp::SignatureHelp {
12538            signatures: vec![lsp::SignatureInformation {
12539                label: "test signature".to_string(),
12540                documentation: None,
12541                parameters: Some(vec![lsp::ParameterInformation {
12542                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12543                    documentation: None,
12544                }]),
12545                active_parameter: None,
12546            }],
12547            active_signature: None,
12548            active_parameter: None,
12549        },
12550    );
12551    cx.update_editor(|editor, window, cx| {
12552        assert!(
12553            !editor.signature_help_state.is_shown(),
12554            "No signature help was called for"
12555        );
12556        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12557    });
12558    cx.run_until_parked();
12559    cx.update_editor(|editor, _, _| {
12560        assert!(
12561            !editor.signature_help_state.is_shown(),
12562            "No signature help should be shown when completions menu is open"
12563        );
12564    });
12565
12566    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12567        editor.context_menu_next(&Default::default(), window, cx);
12568        editor
12569            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12570            .unwrap()
12571    });
12572    cx.assert_editor_state(indoc! {"
12573        one.second_completionˇ
12574        two
12575        three
12576    "});
12577
12578    handle_resolve_completion_request(
12579        &mut cx,
12580        Some(vec![
12581            (
12582                //This overlaps with the primary completion edit which is
12583                //misbehavior from the LSP spec, test that we filter it out
12584                indoc! {"
12585                    one.second_ˇcompletion
12586                    two
12587                    threeˇ
12588                "},
12589                "overlapping additional edit",
12590            ),
12591            (
12592                indoc! {"
12593                    one.second_completion
12594                    two
12595                    threeˇ
12596                "},
12597                "\nadditional edit",
12598            ),
12599        ]),
12600    )
12601    .await;
12602    apply_additional_edits.await.unwrap();
12603    cx.assert_editor_state(indoc! {"
12604        one.second_completionˇ
12605        two
12606        three
12607        additional edit
12608    "});
12609
12610    cx.set_state(indoc! {"
12611        one.second_completion
12612        twoˇ
12613        threeˇ
12614        additional edit
12615    "});
12616    cx.simulate_keystroke(" ");
12617    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12618    cx.simulate_keystroke("s");
12619    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12620
12621    cx.assert_editor_state(indoc! {"
12622        one.second_completion
12623        two sˇ
12624        three sˇ
12625        additional edit
12626    "});
12627    handle_completion_request(
12628        indoc! {"
12629            one.second_completion
12630            two s
12631            three <s|>
12632            additional edit
12633        "},
12634        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12635        true,
12636        counter.clone(),
12637        &mut cx,
12638    )
12639    .await;
12640    cx.condition(|editor, _| editor.context_menu_visible())
12641        .await;
12642    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12643
12644    cx.simulate_keystroke("i");
12645
12646    handle_completion_request(
12647        indoc! {"
12648            one.second_completion
12649            two si
12650            three <si|>
12651            additional edit
12652        "},
12653        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12654        true,
12655        counter.clone(),
12656        &mut cx,
12657    )
12658    .await;
12659    cx.condition(|editor, _| editor.context_menu_visible())
12660        .await;
12661    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12662
12663    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12664        editor
12665            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12666            .unwrap()
12667    });
12668    cx.assert_editor_state(indoc! {"
12669        one.second_completion
12670        two sixth_completionˇ
12671        three sixth_completionˇ
12672        additional edit
12673    "});
12674
12675    apply_additional_edits.await.unwrap();
12676
12677    update_test_language_settings(&mut cx, |settings| {
12678        settings.defaults.show_completions_on_input = Some(false);
12679    });
12680    cx.set_state("editorˇ");
12681    cx.simulate_keystroke(".");
12682    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12683    cx.simulate_keystrokes("c l o");
12684    cx.assert_editor_state("editor.cloˇ");
12685    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12686    cx.update_editor(|editor, window, cx| {
12687        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12688    });
12689    handle_completion_request(
12690        "editor.<clo|>",
12691        vec!["close", "clobber"],
12692        true,
12693        counter.clone(),
12694        &mut cx,
12695    )
12696    .await;
12697    cx.condition(|editor, _| editor.context_menu_visible())
12698        .await;
12699    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12700
12701    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12702        editor
12703            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12704            .unwrap()
12705    });
12706    cx.assert_editor_state("editor.clobberˇ");
12707    handle_resolve_completion_request(&mut cx, None).await;
12708    apply_additional_edits.await.unwrap();
12709}
12710
12711#[gpui::test]
12712async fn test_completion_reuse(cx: &mut TestAppContext) {
12713    init_test(cx, |_| {});
12714
12715    let mut cx = EditorLspTestContext::new_rust(
12716        lsp::ServerCapabilities {
12717            completion_provider: Some(lsp::CompletionOptions {
12718                trigger_characters: Some(vec![".".to_string()]),
12719                ..Default::default()
12720            }),
12721            ..Default::default()
12722        },
12723        cx,
12724    )
12725    .await;
12726
12727    let counter = Arc::new(AtomicUsize::new(0));
12728    cx.set_state("objˇ");
12729    cx.simulate_keystroke(".");
12730
12731    // Initial completion request returns complete results
12732    let is_incomplete = false;
12733    handle_completion_request(
12734        "obj.|<>",
12735        vec!["a", "ab", "abc"],
12736        is_incomplete,
12737        counter.clone(),
12738        &mut cx,
12739    )
12740    .await;
12741    cx.run_until_parked();
12742    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12743    cx.assert_editor_state("obj.ˇ");
12744    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12745
12746    // Type "a" - filters existing completions
12747    cx.simulate_keystroke("a");
12748    cx.run_until_parked();
12749    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12750    cx.assert_editor_state("obj.aˇ");
12751    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12752
12753    // Type "b" - filters existing completions
12754    cx.simulate_keystroke("b");
12755    cx.run_until_parked();
12756    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12757    cx.assert_editor_state("obj.abˇ");
12758    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12759
12760    // Type "c" - filters existing completions
12761    cx.simulate_keystroke("c");
12762    cx.run_until_parked();
12763    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12764    cx.assert_editor_state("obj.abcˇ");
12765    check_displayed_completions(vec!["abc"], &mut cx);
12766
12767    // Backspace to delete "c" - filters existing completions
12768    cx.update_editor(|editor, window, cx| {
12769        editor.backspace(&Backspace, window, cx);
12770    });
12771    cx.run_until_parked();
12772    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12773    cx.assert_editor_state("obj.abˇ");
12774    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12775
12776    // Moving cursor to the left dismisses menu.
12777    cx.update_editor(|editor, window, cx| {
12778        editor.move_left(&MoveLeft, window, cx);
12779    });
12780    cx.run_until_parked();
12781    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12782    cx.assert_editor_state("obj.aˇb");
12783    cx.update_editor(|editor, _, _| {
12784        assert_eq!(editor.context_menu_visible(), false);
12785    });
12786
12787    // Type "b" - new request
12788    cx.simulate_keystroke("b");
12789    let is_incomplete = false;
12790    handle_completion_request(
12791        "obj.<ab|>a",
12792        vec!["ab", "abc"],
12793        is_incomplete,
12794        counter.clone(),
12795        &mut cx,
12796    )
12797    .await;
12798    cx.run_until_parked();
12799    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12800    cx.assert_editor_state("obj.abˇb");
12801    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12802
12803    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12804    cx.update_editor(|editor, window, cx| {
12805        editor.backspace(&Backspace, window, cx);
12806    });
12807    let is_incomplete = false;
12808    handle_completion_request(
12809        "obj.<a|>b",
12810        vec!["a", "ab", "abc"],
12811        is_incomplete,
12812        counter.clone(),
12813        &mut cx,
12814    )
12815    .await;
12816    cx.run_until_parked();
12817    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12818    cx.assert_editor_state("obj.aˇb");
12819    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12820
12821    // Backspace to delete "a" - dismisses menu.
12822    cx.update_editor(|editor, window, cx| {
12823        editor.backspace(&Backspace, window, cx);
12824    });
12825    cx.run_until_parked();
12826    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12827    cx.assert_editor_state("obj.ˇb");
12828    cx.update_editor(|editor, _, _| {
12829        assert_eq!(editor.context_menu_visible(), false);
12830    });
12831}
12832
12833#[gpui::test]
12834async fn test_word_completion(cx: &mut TestAppContext) {
12835    let lsp_fetch_timeout_ms = 10;
12836    init_test(cx, |language_settings| {
12837        language_settings.defaults.completions = Some(CompletionSettings {
12838            words: WordsCompletionMode::Fallback,
12839            lsp: true,
12840            lsp_fetch_timeout_ms: 10,
12841            lsp_insert_mode: LspInsertMode::Insert,
12842        });
12843    });
12844
12845    let mut cx = EditorLspTestContext::new_rust(
12846        lsp::ServerCapabilities {
12847            completion_provider: Some(lsp::CompletionOptions {
12848                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12849                ..lsp::CompletionOptions::default()
12850            }),
12851            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12852            ..lsp::ServerCapabilities::default()
12853        },
12854        cx,
12855    )
12856    .await;
12857
12858    let throttle_completions = Arc::new(AtomicBool::new(false));
12859
12860    let lsp_throttle_completions = throttle_completions.clone();
12861    let _completion_requests_handler =
12862        cx.lsp
12863            .server
12864            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12865                let lsp_throttle_completions = lsp_throttle_completions.clone();
12866                let cx = cx.clone();
12867                async move {
12868                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12869                        cx.background_executor()
12870                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12871                            .await;
12872                    }
12873                    Ok(Some(lsp::CompletionResponse::Array(vec![
12874                        lsp::CompletionItem {
12875                            label: "first".into(),
12876                            ..lsp::CompletionItem::default()
12877                        },
12878                        lsp::CompletionItem {
12879                            label: "last".into(),
12880                            ..lsp::CompletionItem::default()
12881                        },
12882                    ])))
12883                }
12884            });
12885
12886    cx.set_state(indoc! {"
12887        oneˇ
12888        two
12889        three
12890    "});
12891    cx.simulate_keystroke(".");
12892    cx.executor().run_until_parked();
12893    cx.condition(|editor, _| editor.context_menu_visible())
12894        .await;
12895    cx.update_editor(|editor, window, cx| {
12896        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12897        {
12898            assert_eq!(
12899                completion_menu_entries(&menu),
12900                &["first", "last"],
12901                "When LSP server is fast to reply, no fallback word completions are used"
12902            );
12903        } else {
12904            panic!("expected completion menu to be open");
12905        }
12906        editor.cancel(&Cancel, window, cx);
12907    });
12908    cx.executor().run_until_parked();
12909    cx.condition(|editor, _| !editor.context_menu_visible())
12910        .await;
12911
12912    throttle_completions.store(true, atomic::Ordering::Release);
12913    cx.simulate_keystroke(".");
12914    cx.executor()
12915        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12916    cx.executor().run_until_parked();
12917    cx.condition(|editor, _| editor.context_menu_visible())
12918        .await;
12919    cx.update_editor(|editor, _, _| {
12920        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12921        {
12922            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12923                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12924        } else {
12925            panic!("expected completion menu to be open");
12926        }
12927    });
12928}
12929
12930#[gpui::test]
12931async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12932    init_test(cx, |language_settings| {
12933        language_settings.defaults.completions = Some(CompletionSettings {
12934            words: WordsCompletionMode::Enabled,
12935            lsp: true,
12936            lsp_fetch_timeout_ms: 0,
12937            lsp_insert_mode: LspInsertMode::Insert,
12938        });
12939    });
12940
12941    let mut cx = EditorLspTestContext::new_rust(
12942        lsp::ServerCapabilities {
12943            completion_provider: Some(lsp::CompletionOptions {
12944                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12945                ..lsp::CompletionOptions::default()
12946            }),
12947            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12948            ..lsp::ServerCapabilities::default()
12949        },
12950        cx,
12951    )
12952    .await;
12953
12954    let _completion_requests_handler =
12955        cx.lsp
12956            .server
12957            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12958                Ok(Some(lsp::CompletionResponse::Array(vec![
12959                    lsp::CompletionItem {
12960                        label: "first".into(),
12961                        ..lsp::CompletionItem::default()
12962                    },
12963                    lsp::CompletionItem {
12964                        label: "last".into(),
12965                        ..lsp::CompletionItem::default()
12966                    },
12967                ])))
12968            });
12969
12970    cx.set_state(indoc! {"ˇ
12971        first
12972        last
12973        second
12974    "});
12975    cx.simulate_keystroke(".");
12976    cx.executor().run_until_parked();
12977    cx.condition(|editor, _| editor.context_menu_visible())
12978        .await;
12979    cx.update_editor(|editor, _, _| {
12980        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12981        {
12982            assert_eq!(
12983                completion_menu_entries(&menu),
12984                &["first", "last", "second"],
12985                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12986            );
12987        } else {
12988            panic!("expected completion menu to be open");
12989        }
12990    });
12991}
12992
12993#[gpui::test]
12994async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12995    init_test(cx, |language_settings| {
12996        language_settings.defaults.completions = Some(CompletionSettings {
12997            words: WordsCompletionMode::Disabled,
12998            lsp: true,
12999            lsp_fetch_timeout_ms: 0,
13000            lsp_insert_mode: LspInsertMode::Insert,
13001        });
13002    });
13003
13004    let mut cx = EditorLspTestContext::new_rust(
13005        lsp::ServerCapabilities {
13006            completion_provider: Some(lsp::CompletionOptions {
13007                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13008                ..lsp::CompletionOptions::default()
13009            }),
13010            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13011            ..lsp::ServerCapabilities::default()
13012        },
13013        cx,
13014    )
13015    .await;
13016
13017    let _completion_requests_handler =
13018        cx.lsp
13019            .server
13020            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13021                panic!("LSP completions should not be queried when dealing with word completions")
13022            });
13023
13024    cx.set_state(indoc! {"ˇ
13025        first
13026        last
13027        second
13028    "});
13029    cx.update_editor(|editor, window, cx| {
13030        editor.show_word_completions(&ShowWordCompletions, window, cx);
13031    });
13032    cx.executor().run_until_parked();
13033    cx.condition(|editor, _| editor.context_menu_visible())
13034        .await;
13035    cx.update_editor(|editor, _, _| {
13036        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13037        {
13038            assert_eq!(
13039                completion_menu_entries(&menu),
13040                &["first", "last", "second"],
13041                "`ShowWordCompletions` action should show word completions"
13042            );
13043        } else {
13044            panic!("expected completion menu to be open");
13045        }
13046    });
13047
13048    cx.simulate_keystroke("l");
13049    cx.executor().run_until_parked();
13050    cx.condition(|editor, _| editor.context_menu_visible())
13051        .await;
13052    cx.update_editor(|editor, _, _| {
13053        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13054        {
13055            assert_eq!(
13056                completion_menu_entries(&menu),
13057                &["last"],
13058                "After showing word completions, further editing should filter them and not query the LSP"
13059            );
13060        } else {
13061            panic!("expected completion menu to be open");
13062        }
13063    });
13064}
13065
13066#[gpui::test]
13067async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13068    init_test(cx, |language_settings| {
13069        language_settings.defaults.completions = Some(CompletionSettings {
13070            words: WordsCompletionMode::Fallback,
13071            lsp: false,
13072            lsp_fetch_timeout_ms: 0,
13073            lsp_insert_mode: LspInsertMode::Insert,
13074        });
13075    });
13076
13077    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13078
13079    cx.set_state(indoc! {"ˇ
13080        0_usize
13081        let
13082        33
13083        4.5f32
13084    "});
13085    cx.update_editor(|editor, window, cx| {
13086        editor.show_completions(&ShowCompletions::default(), window, cx);
13087    });
13088    cx.executor().run_until_parked();
13089    cx.condition(|editor, _| editor.context_menu_visible())
13090        .await;
13091    cx.update_editor(|editor, window, cx| {
13092        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13093        {
13094            assert_eq!(
13095                completion_menu_entries(&menu),
13096                &["let"],
13097                "With no digits in the completion query, no digits should be in the word completions"
13098            );
13099        } else {
13100            panic!("expected completion menu to be open");
13101        }
13102        editor.cancel(&Cancel, window, cx);
13103    });
13104
13105    cx.set_state(indoc! {"13106        0_usize
13107        let
13108        3
13109        33.35f32
13110    "});
13111    cx.update_editor(|editor, window, cx| {
13112        editor.show_completions(&ShowCompletions::default(), window, cx);
13113    });
13114    cx.executor().run_until_parked();
13115    cx.condition(|editor, _| editor.context_menu_visible())
13116        .await;
13117    cx.update_editor(|editor, _, _| {
13118        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13119        {
13120            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13121                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13122        } else {
13123            panic!("expected completion menu to be open");
13124        }
13125    });
13126}
13127
13128fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13129    let position = || lsp::Position {
13130        line: params.text_document_position.position.line,
13131        character: params.text_document_position.position.character,
13132    };
13133    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13134        range: lsp::Range {
13135            start: position(),
13136            end: position(),
13137        },
13138        new_text: text.to_string(),
13139    }))
13140}
13141
13142#[gpui::test]
13143async fn test_multiline_completion(cx: &mut TestAppContext) {
13144    init_test(cx, |_| {});
13145
13146    let fs = FakeFs::new(cx.executor());
13147    fs.insert_tree(
13148        path!("/a"),
13149        json!({
13150            "main.ts": "a",
13151        }),
13152    )
13153    .await;
13154
13155    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13156    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13157    let typescript_language = Arc::new(Language::new(
13158        LanguageConfig {
13159            name: "TypeScript".into(),
13160            matcher: LanguageMatcher {
13161                path_suffixes: vec!["ts".to_string()],
13162                ..LanguageMatcher::default()
13163            },
13164            line_comments: vec!["// ".into()],
13165            ..LanguageConfig::default()
13166        },
13167        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13168    ));
13169    language_registry.add(typescript_language.clone());
13170    let mut fake_servers = language_registry.register_fake_lsp(
13171        "TypeScript",
13172        FakeLspAdapter {
13173            capabilities: lsp::ServerCapabilities {
13174                completion_provider: Some(lsp::CompletionOptions {
13175                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13176                    ..lsp::CompletionOptions::default()
13177                }),
13178                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13179                ..lsp::ServerCapabilities::default()
13180            },
13181            // Emulate vtsls label generation
13182            label_for_completion: Some(Box::new(|item, _| {
13183                let text = if let Some(description) = item
13184                    .label_details
13185                    .as_ref()
13186                    .and_then(|label_details| label_details.description.as_ref())
13187                {
13188                    format!("{} {}", item.label, description)
13189                } else if let Some(detail) = &item.detail {
13190                    format!("{} {}", item.label, detail)
13191                } else {
13192                    item.label.clone()
13193                };
13194                let len = text.len();
13195                Some(language::CodeLabel {
13196                    text,
13197                    runs: Vec::new(),
13198                    filter_range: 0..len,
13199                })
13200            })),
13201            ..FakeLspAdapter::default()
13202        },
13203    );
13204    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13205    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13206    let worktree_id = workspace
13207        .update(cx, |workspace, _window, cx| {
13208            workspace.project().update(cx, |project, cx| {
13209                project.worktrees(cx).next().unwrap().read(cx).id()
13210            })
13211        })
13212        .unwrap();
13213    let _buffer = project
13214        .update(cx, |project, cx| {
13215            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13216        })
13217        .await
13218        .unwrap();
13219    let editor = workspace
13220        .update(cx, |workspace, window, cx| {
13221            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13222        })
13223        .unwrap()
13224        .await
13225        .unwrap()
13226        .downcast::<Editor>()
13227        .unwrap();
13228    let fake_server = fake_servers.next().await.unwrap();
13229
13230    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13231    let multiline_label_2 = "a\nb\nc\n";
13232    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13233    let multiline_description = "d\ne\nf\n";
13234    let multiline_detail_2 = "g\nh\ni\n";
13235
13236    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13237        move |params, _| async move {
13238            Ok(Some(lsp::CompletionResponse::Array(vec![
13239                lsp::CompletionItem {
13240                    label: multiline_label.to_string(),
13241                    text_edit: gen_text_edit(&params, "new_text_1"),
13242                    ..lsp::CompletionItem::default()
13243                },
13244                lsp::CompletionItem {
13245                    label: "single line label 1".to_string(),
13246                    detail: Some(multiline_detail.to_string()),
13247                    text_edit: gen_text_edit(&params, "new_text_2"),
13248                    ..lsp::CompletionItem::default()
13249                },
13250                lsp::CompletionItem {
13251                    label: "single line label 2".to_string(),
13252                    label_details: Some(lsp::CompletionItemLabelDetails {
13253                        description: Some(multiline_description.to_string()),
13254                        detail: None,
13255                    }),
13256                    text_edit: gen_text_edit(&params, "new_text_2"),
13257                    ..lsp::CompletionItem::default()
13258                },
13259                lsp::CompletionItem {
13260                    label: multiline_label_2.to_string(),
13261                    detail: Some(multiline_detail_2.to_string()),
13262                    text_edit: gen_text_edit(&params, "new_text_3"),
13263                    ..lsp::CompletionItem::default()
13264                },
13265                lsp::CompletionItem {
13266                    label: "Label with many     spaces and \t but without newlines".to_string(),
13267                    detail: Some(
13268                        "Details with many     spaces and \t but without newlines".to_string(),
13269                    ),
13270                    text_edit: gen_text_edit(&params, "new_text_4"),
13271                    ..lsp::CompletionItem::default()
13272                },
13273            ])))
13274        },
13275    );
13276
13277    editor.update_in(cx, |editor, window, cx| {
13278        cx.focus_self(window);
13279        editor.move_to_end(&MoveToEnd, window, cx);
13280        editor.handle_input(".", window, cx);
13281    });
13282    cx.run_until_parked();
13283    completion_handle.next().await.unwrap();
13284
13285    editor.update(cx, |editor, _| {
13286        assert!(editor.context_menu_visible());
13287        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13288        {
13289            let completion_labels = menu
13290                .completions
13291                .borrow()
13292                .iter()
13293                .map(|c| c.label.text.clone())
13294                .collect::<Vec<_>>();
13295            assert_eq!(
13296                completion_labels,
13297                &[
13298                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13299                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13300                    "single line label 2 d e f ",
13301                    "a b c g h i ",
13302                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13303                ],
13304                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13305            );
13306
13307            for completion in menu
13308                .completions
13309                .borrow()
13310                .iter() {
13311                    assert_eq!(
13312                        completion.label.filter_range,
13313                        0..completion.label.text.len(),
13314                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13315                    );
13316                }
13317        } else {
13318            panic!("expected completion menu to be open");
13319        }
13320    });
13321}
13322
13323#[gpui::test]
13324async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13325    init_test(cx, |_| {});
13326    let mut cx = EditorLspTestContext::new_rust(
13327        lsp::ServerCapabilities {
13328            completion_provider: Some(lsp::CompletionOptions {
13329                trigger_characters: Some(vec![".".to_string()]),
13330                ..Default::default()
13331            }),
13332            ..Default::default()
13333        },
13334        cx,
13335    )
13336    .await;
13337    cx.lsp
13338        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13339            Ok(Some(lsp::CompletionResponse::Array(vec![
13340                lsp::CompletionItem {
13341                    label: "first".into(),
13342                    ..Default::default()
13343                },
13344                lsp::CompletionItem {
13345                    label: "last".into(),
13346                    ..Default::default()
13347                },
13348            ])))
13349        });
13350    cx.set_state("variableˇ");
13351    cx.simulate_keystroke(".");
13352    cx.executor().run_until_parked();
13353
13354    cx.update_editor(|editor, _, _| {
13355        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13356        {
13357            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13358        } else {
13359            panic!("expected completion menu to be open");
13360        }
13361    });
13362
13363    cx.update_editor(|editor, window, cx| {
13364        editor.move_page_down(&MovePageDown::default(), window, cx);
13365        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13366        {
13367            assert!(
13368                menu.selected_item == 1,
13369                "expected PageDown to select the last item from the context menu"
13370            );
13371        } else {
13372            panic!("expected completion menu to stay open after PageDown");
13373        }
13374    });
13375
13376    cx.update_editor(|editor, window, cx| {
13377        editor.move_page_up(&MovePageUp::default(), window, cx);
13378        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13379        {
13380            assert!(
13381                menu.selected_item == 0,
13382                "expected PageUp to select the first item from the context menu"
13383            );
13384        } else {
13385            panic!("expected completion menu to stay open after PageUp");
13386        }
13387    });
13388}
13389
13390#[gpui::test]
13391async fn test_as_is_completions(cx: &mut TestAppContext) {
13392    init_test(cx, |_| {});
13393    let mut cx = EditorLspTestContext::new_rust(
13394        lsp::ServerCapabilities {
13395            completion_provider: Some(lsp::CompletionOptions {
13396                ..Default::default()
13397            }),
13398            ..Default::default()
13399        },
13400        cx,
13401    )
13402    .await;
13403    cx.lsp
13404        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13405            Ok(Some(lsp::CompletionResponse::Array(vec![
13406                lsp::CompletionItem {
13407                    label: "unsafe".into(),
13408                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13409                        range: lsp::Range {
13410                            start: lsp::Position {
13411                                line: 1,
13412                                character: 2,
13413                            },
13414                            end: lsp::Position {
13415                                line: 1,
13416                                character: 3,
13417                            },
13418                        },
13419                        new_text: "unsafe".to_string(),
13420                    })),
13421                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13422                    ..Default::default()
13423                },
13424            ])))
13425        });
13426    cx.set_state("fn a() {}\n");
13427    cx.executor().run_until_parked();
13428    cx.update_editor(|editor, window, cx| {
13429        editor.show_completions(
13430            &ShowCompletions {
13431                trigger: Some("\n".into()),
13432            },
13433            window,
13434            cx,
13435        );
13436    });
13437    cx.executor().run_until_parked();
13438
13439    cx.update_editor(|editor, window, cx| {
13440        editor.confirm_completion(&Default::default(), window, cx)
13441    });
13442    cx.executor().run_until_parked();
13443    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13444}
13445
13446#[gpui::test]
13447async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13448    init_test(cx, |_| {});
13449
13450    let mut cx = EditorLspTestContext::new_rust(
13451        lsp::ServerCapabilities {
13452            completion_provider: Some(lsp::CompletionOptions {
13453                trigger_characters: Some(vec![".".to_string()]),
13454                resolve_provider: Some(true),
13455                ..Default::default()
13456            }),
13457            ..Default::default()
13458        },
13459        cx,
13460    )
13461    .await;
13462
13463    cx.set_state("fn main() { let a = 2ˇ; }");
13464    cx.simulate_keystroke(".");
13465    let completion_item = lsp::CompletionItem {
13466        label: "Some".into(),
13467        kind: Some(lsp::CompletionItemKind::SNIPPET),
13468        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13469        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13470            kind: lsp::MarkupKind::Markdown,
13471            value: "```rust\nSome(2)\n```".to_string(),
13472        })),
13473        deprecated: Some(false),
13474        sort_text: Some("Some".to_string()),
13475        filter_text: Some("Some".to_string()),
13476        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13477        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13478            range: lsp::Range {
13479                start: lsp::Position {
13480                    line: 0,
13481                    character: 22,
13482                },
13483                end: lsp::Position {
13484                    line: 0,
13485                    character: 22,
13486                },
13487            },
13488            new_text: "Some(2)".to_string(),
13489        })),
13490        additional_text_edits: Some(vec![lsp::TextEdit {
13491            range: lsp::Range {
13492                start: lsp::Position {
13493                    line: 0,
13494                    character: 20,
13495                },
13496                end: lsp::Position {
13497                    line: 0,
13498                    character: 22,
13499                },
13500            },
13501            new_text: "".to_string(),
13502        }]),
13503        ..Default::default()
13504    };
13505
13506    let closure_completion_item = completion_item.clone();
13507    let counter = Arc::new(AtomicUsize::new(0));
13508    let counter_clone = counter.clone();
13509    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13510        let task_completion_item = closure_completion_item.clone();
13511        counter_clone.fetch_add(1, atomic::Ordering::Release);
13512        async move {
13513            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13514                is_incomplete: true,
13515                item_defaults: None,
13516                items: vec![task_completion_item],
13517            })))
13518        }
13519    });
13520
13521    cx.condition(|editor, _| editor.context_menu_visible())
13522        .await;
13523    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13524    assert!(request.next().await.is_some());
13525    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13526
13527    cx.simulate_keystrokes("S o m");
13528    cx.condition(|editor, _| editor.context_menu_visible())
13529        .await;
13530    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13531    assert!(request.next().await.is_some());
13532    assert!(request.next().await.is_some());
13533    assert!(request.next().await.is_some());
13534    request.close();
13535    assert!(request.next().await.is_none());
13536    assert_eq!(
13537        counter.load(atomic::Ordering::Acquire),
13538        4,
13539        "With the completions menu open, only one LSP request should happen per input"
13540    );
13541}
13542
13543#[gpui::test]
13544async fn test_toggle_comment(cx: &mut TestAppContext) {
13545    init_test(cx, |_| {});
13546    let mut cx = EditorTestContext::new(cx).await;
13547    let language = Arc::new(Language::new(
13548        LanguageConfig {
13549            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13550            ..Default::default()
13551        },
13552        Some(tree_sitter_rust::LANGUAGE.into()),
13553    ));
13554    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13555
13556    // If multiple selections intersect a line, the line is only toggled once.
13557    cx.set_state(indoc! {"
13558        fn a() {
13559            «//b();
13560            ˇ»// «c();
13561            //ˇ»  d();
13562        }
13563    "});
13564
13565    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13566
13567    cx.assert_editor_state(indoc! {"
13568        fn a() {
13569            «b();
13570            c();
13571            ˇ» d();
13572        }
13573    "});
13574
13575    // The comment prefix is inserted at the same column for every line in a
13576    // selection.
13577    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13578
13579    cx.assert_editor_state(indoc! {"
13580        fn a() {
13581            // «b();
13582            // c();
13583            ˇ»//  d();
13584        }
13585    "});
13586
13587    // If a selection ends at the beginning of a line, that line is not toggled.
13588    cx.set_selections_state(indoc! {"
13589        fn a() {
13590            // b();
13591            «// c();
13592        ˇ»    //  d();
13593        }
13594    "});
13595
13596    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13597
13598    cx.assert_editor_state(indoc! {"
13599        fn a() {
13600            // b();
13601            «c();
13602        ˇ»    //  d();
13603        }
13604    "});
13605
13606    // If a selection span a single line and is empty, the line is toggled.
13607    cx.set_state(indoc! {"
13608        fn a() {
13609            a();
13610            b();
13611        ˇ
13612        }
13613    "});
13614
13615    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13616
13617    cx.assert_editor_state(indoc! {"
13618        fn a() {
13619            a();
13620            b();
13621        //•ˇ
13622        }
13623    "});
13624
13625    // If a selection span multiple lines, empty lines are not toggled.
13626    cx.set_state(indoc! {"
13627        fn a() {
13628            «a();
13629
13630            c();ˇ»
13631        }
13632    "});
13633
13634    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13635
13636    cx.assert_editor_state(indoc! {"
13637        fn a() {
13638            // «a();
13639
13640            // c();ˇ»
13641        }
13642    "});
13643
13644    // If a selection includes multiple comment prefixes, all lines are uncommented.
13645    cx.set_state(indoc! {"
13646        fn a() {
13647            «// a();
13648            /// b();
13649            //! c();ˇ»
13650        }
13651    "});
13652
13653    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13654
13655    cx.assert_editor_state(indoc! {"
13656        fn a() {
13657            «a();
13658            b();
13659            c();ˇ»
13660        }
13661    "});
13662}
13663
13664#[gpui::test]
13665async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13666    init_test(cx, |_| {});
13667    let mut cx = EditorTestContext::new(cx).await;
13668    let language = Arc::new(Language::new(
13669        LanguageConfig {
13670            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13671            ..Default::default()
13672        },
13673        Some(tree_sitter_rust::LANGUAGE.into()),
13674    ));
13675    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13676
13677    let toggle_comments = &ToggleComments {
13678        advance_downwards: false,
13679        ignore_indent: true,
13680    };
13681
13682    // If multiple selections intersect a line, the line is only toggled once.
13683    cx.set_state(indoc! {"
13684        fn a() {
13685        //    «b();
13686        //    c();
13687        //    ˇ» d();
13688        }
13689    "});
13690
13691    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13692
13693    cx.assert_editor_state(indoc! {"
13694        fn a() {
13695            «b();
13696            c();
13697            ˇ» d();
13698        }
13699    "});
13700
13701    // The comment prefix is inserted at the beginning of each line
13702    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13703
13704    cx.assert_editor_state(indoc! {"
13705        fn a() {
13706        //    «b();
13707        //    c();
13708        //    ˇ» d();
13709        }
13710    "});
13711
13712    // If a selection ends at the beginning of a line, that line is not toggled.
13713    cx.set_selections_state(indoc! {"
13714        fn a() {
13715        //    b();
13716        //    «c();
13717        ˇ»//     d();
13718        }
13719    "});
13720
13721    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13722
13723    cx.assert_editor_state(indoc! {"
13724        fn a() {
13725        //    b();
13726            «c();
13727        ˇ»//     d();
13728        }
13729    "});
13730
13731    // If a selection span a single line and is empty, the line is toggled.
13732    cx.set_state(indoc! {"
13733        fn a() {
13734            a();
13735            b();
13736        ˇ
13737        }
13738    "});
13739
13740    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13741
13742    cx.assert_editor_state(indoc! {"
13743        fn a() {
13744            a();
13745            b();
13746        //ˇ
13747        }
13748    "});
13749
13750    // If a selection span multiple lines, empty lines are not toggled.
13751    cx.set_state(indoc! {"
13752        fn a() {
13753            «a();
13754
13755            c();ˇ»
13756        }
13757    "});
13758
13759    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13760
13761    cx.assert_editor_state(indoc! {"
13762        fn a() {
13763        //    «a();
13764
13765        //    c();ˇ»
13766        }
13767    "});
13768
13769    // If a selection includes multiple comment prefixes, all lines are uncommented.
13770    cx.set_state(indoc! {"
13771        fn a() {
13772        //    «a();
13773        ///    b();
13774        //!    c();ˇ»
13775        }
13776    "});
13777
13778    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13779
13780    cx.assert_editor_state(indoc! {"
13781        fn a() {
13782            «a();
13783            b();
13784            c();ˇ»
13785        }
13786    "});
13787}
13788
13789#[gpui::test]
13790async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13791    init_test(cx, |_| {});
13792
13793    let language = Arc::new(Language::new(
13794        LanguageConfig {
13795            line_comments: vec!["// ".into()],
13796            ..Default::default()
13797        },
13798        Some(tree_sitter_rust::LANGUAGE.into()),
13799    ));
13800
13801    let mut cx = EditorTestContext::new(cx).await;
13802
13803    cx.language_registry().add(language.clone());
13804    cx.update_buffer(|buffer, cx| {
13805        buffer.set_language(Some(language), cx);
13806    });
13807
13808    let toggle_comments = &ToggleComments {
13809        advance_downwards: true,
13810        ignore_indent: false,
13811    };
13812
13813    // Single cursor on one line -> advance
13814    // Cursor moves horizontally 3 characters as well on non-blank line
13815    cx.set_state(indoc!(
13816        "fn a() {
13817             ˇdog();
13818             cat();
13819        }"
13820    ));
13821    cx.update_editor(|editor, window, cx| {
13822        editor.toggle_comments(toggle_comments, window, cx);
13823    });
13824    cx.assert_editor_state(indoc!(
13825        "fn a() {
13826             // dog();
13827             catˇ();
13828        }"
13829    ));
13830
13831    // Single selection on one line -> don't advance
13832    cx.set_state(indoc!(
13833        "fn a() {
13834             «dog()ˇ»;
13835             cat();
13836        }"
13837    ));
13838    cx.update_editor(|editor, window, cx| {
13839        editor.toggle_comments(toggle_comments, window, cx);
13840    });
13841    cx.assert_editor_state(indoc!(
13842        "fn a() {
13843             // «dog()ˇ»;
13844             cat();
13845        }"
13846    ));
13847
13848    // Multiple cursors on one line -> advance
13849    cx.set_state(indoc!(
13850        "fn a() {
13851             ˇdˇog();
13852             cat();
13853        }"
13854    ));
13855    cx.update_editor(|editor, window, cx| {
13856        editor.toggle_comments(toggle_comments, window, cx);
13857    });
13858    cx.assert_editor_state(indoc!(
13859        "fn a() {
13860             // dog();
13861             catˇ(ˇ);
13862        }"
13863    ));
13864
13865    // Multiple cursors on one line, with selection -> don't advance
13866    cx.set_state(indoc!(
13867        "fn a() {
13868             ˇdˇog«()ˇ»;
13869             cat();
13870        }"
13871    ));
13872    cx.update_editor(|editor, window, cx| {
13873        editor.toggle_comments(toggle_comments, window, cx);
13874    });
13875    cx.assert_editor_state(indoc!(
13876        "fn a() {
13877             // ˇdˇog«()ˇ»;
13878             cat();
13879        }"
13880    ));
13881
13882    // Single cursor on one line -> advance
13883    // Cursor moves to column 0 on blank line
13884    cx.set_state(indoc!(
13885        "fn a() {
13886             ˇdog();
13887
13888             cat();
13889        }"
13890    ));
13891    cx.update_editor(|editor, window, cx| {
13892        editor.toggle_comments(toggle_comments, window, cx);
13893    });
13894    cx.assert_editor_state(indoc!(
13895        "fn a() {
13896             // dog();
13897        ˇ
13898             cat();
13899        }"
13900    ));
13901
13902    // Single cursor on one line -> advance
13903    // Cursor starts and ends at column 0
13904    cx.set_state(indoc!(
13905        "fn a() {
13906         ˇ    dog();
13907             cat();
13908        }"
13909    ));
13910    cx.update_editor(|editor, window, cx| {
13911        editor.toggle_comments(toggle_comments, window, cx);
13912    });
13913    cx.assert_editor_state(indoc!(
13914        "fn a() {
13915             // dog();
13916         ˇ    cat();
13917        }"
13918    ));
13919}
13920
13921#[gpui::test]
13922async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13923    init_test(cx, |_| {});
13924
13925    let mut cx = EditorTestContext::new(cx).await;
13926
13927    let html_language = Arc::new(
13928        Language::new(
13929            LanguageConfig {
13930                name: "HTML".into(),
13931                block_comment: Some(BlockCommentConfig {
13932                    start: "<!-- ".into(),
13933                    prefix: "".into(),
13934                    end: " -->".into(),
13935                    tab_size: 0,
13936                }),
13937                ..Default::default()
13938            },
13939            Some(tree_sitter_html::LANGUAGE.into()),
13940        )
13941        .with_injection_query(
13942            r#"
13943            (script_element
13944                (raw_text) @injection.content
13945                (#set! injection.language "javascript"))
13946            "#,
13947        )
13948        .unwrap(),
13949    );
13950
13951    let javascript_language = Arc::new(Language::new(
13952        LanguageConfig {
13953            name: "JavaScript".into(),
13954            line_comments: vec!["// ".into()],
13955            ..Default::default()
13956        },
13957        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13958    ));
13959
13960    cx.language_registry().add(html_language.clone());
13961    cx.language_registry().add(javascript_language.clone());
13962    cx.update_buffer(|buffer, cx| {
13963        buffer.set_language(Some(html_language), cx);
13964    });
13965
13966    // Toggle comments for empty selections
13967    cx.set_state(
13968        &r#"
13969            <p>A</p>ˇ
13970            <p>B</p>ˇ
13971            <p>C</p>ˇ
13972        "#
13973        .unindent(),
13974    );
13975    cx.update_editor(|editor, window, cx| {
13976        editor.toggle_comments(&ToggleComments::default(), window, cx)
13977    });
13978    cx.assert_editor_state(
13979        &r#"
13980            <!-- <p>A</p>ˇ -->
13981            <!-- <p>B</p>ˇ -->
13982            <!-- <p>C</p>ˇ -->
13983        "#
13984        .unindent(),
13985    );
13986    cx.update_editor(|editor, window, cx| {
13987        editor.toggle_comments(&ToggleComments::default(), window, cx)
13988    });
13989    cx.assert_editor_state(
13990        &r#"
13991            <p>A</p>ˇ
13992            <p>B</p>ˇ
13993            <p>C</p>ˇ
13994        "#
13995        .unindent(),
13996    );
13997
13998    // Toggle comments for mixture of empty and non-empty selections, where
13999    // multiple selections occupy a given line.
14000    cx.set_state(
14001        &r#"
14002            <p>A«</p>
14003            <p>ˇ»B</p>ˇ
14004            <p>C«</p>
14005            <p>ˇ»D</p>ˇ
14006        "#
14007        .unindent(),
14008    );
14009
14010    cx.update_editor(|editor, window, cx| {
14011        editor.toggle_comments(&ToggleComments::default(), window, cx)
14012    });
14013    cx.assert_editor_state(
14014        &r#"
14015            <!-- <p>A«</p>
14016            <p>ˇ»B</p>ˇ -->
14017            <!-- <p>C«</p>
14018            <p>ˇ»D</p>ˇ -->
14019        "#
14020        .unindent(),
14021    );
14022    cx.update_editor(|editor, window, cx| {
14023        editor.toggle_comments(&ToggleComments::default(), window, cx)
14024    });
14025    cx.assert_editor_state(
14026        &r#"
14027            <p>A«</p>
14028            <p>ˇ»B</p>ˇ
14029            <p>C«</p>
14030            <p>ˇ»D</p>ˇ
14031        "#
14032        .unindent(),
14033    );
14034
14035    // Toggle comments when different languages are active for different
14036    // selections.
14037    cx.set_state(
14038        &r#"
14039            ˇ<script>
14040                ˇvar x = new Y();
14041            ˇ</script>
14042        "#
14043        .unindent(),
14044    );
14045    cx.executor().run_until_parked();
14046    cx.update_editor(|editor, window, cx| {
14047        editor.toggle_comments(&ToggleComments::default(), window, cx)
14048    });
14049    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14050    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14051    cx.assert_editor_state(
14052        &r#"
14053            <!-- ˇ<script> -->
14054                // ˇvar x = new Y();
14055            <!-- ˇ</script> -->
14056        "#
14057        .unindent(),
14058    );
14059}
14060
14061#[gpui::test]
14062fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14063    init_test(cx, |_| {});
14064
14065    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14066    let multibuffer = cx.new(|cx| {
14067        let mut multibuffer = MultiBuffer::new(ReadWrite);
14068        multibuffer.push_excerpts(
14069            buffer.clone(),
14070            [
14071                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14072                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14073            ],
14074            cx,
14075        );
14076        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14077        multibuffer
14078    });
14079
14080    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14081    editor.update_in(cx, |editor, window, cx| {
14082        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14084            s.select_ranges([
14085                Point::new(0, 0)..Point::new(0, 0),
14086                Point::new(1, 0)..Point::new(1, 0),
14087            ])
14088        });
14089
14090        editor.handle_input("X", window, cx);
14091        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14092        assert_eq!(
14093            editor.selections.ranges(cx),
14094            [
14095                Point::new(0, 1)..Point::new(0, 1),
14096                Point::new(1, 1)..Point::new(1, 1),
14097            ]
14098        );
14099
14100        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14101        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14102            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14103        });
14104        editor.backspace(&Default::default(), window, cx);
14105        assert_eq!(editor.text(cx), "Xa\nbbb");
14106        assert_eq!(
14107            editor.selections.ranges(cx),
14108            [Point::new(1, 0)..Point::new(1, 0)]
14109        );
14110
14111        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14112            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14113        });
14114        editor.backspace(&Default::default(), window, cx);
14115        assert_eq!(editor.text(cx), "X\nbb");
14116        assert_eq!(
14117            editor.selections.ranges(cx),
14118            [Point::new(0, 1)..Point::new(0, 1)]
14119        );
14120    });
14121}
14122
14123#[gpui::test]
14124fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14125    init_test(cx, |_| {});
14126
14127    let markers = vec![('[', ']').into(), ('(', ')').into()];
14128    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14129        indoc! {"
14130            [aaaa
14131            (bbbb]
14132            cccc)",
14133        },
14134        markers.clone(),
14135    );
14136    let excerpt_ranges = markers.into_iter().map(|marker| {
14137        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14138        ExcerptRange::new(context.clone())
14139    });
14140    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14141    let multibuffer = cx.new(|cx| {
14142        let mut multibuffer = MultiBuffer::new(ReadWrite);
14143        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14144        multibuffer
14145    });
14146
14147    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14148    editor.update_in(cx, |editor, window, cx| {
14149        let (expected_text, selection_ranges) = marked_text_ranges(
14150            indoc! {"
14151                aaaa
14152                bˇbbb
14153                bˇbbˇb
14154                cccc"
14155            },
14156            true,
14157        );
14158        assert_eq!(editor.text(cx), expected_text);
14159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14160            s.select_ranges(selection_ranges)
14161        });
14162
14163        editor.handle_input("X", window, cx);
14164
14165        let (expected_text, expected_selections) = marked_text_ranges(
14166            indoc! {"
14167                aaaa
14168                bXˇbbXb
14169                bXˇbbXˇb
14170                cccc"
14171            },
14172            false,
14173        );
14174        assert_eq!(editor.text(cx), expected_text);
14175        assert_eq!(editor.selections.ranges(cx), expected_selections);
14176
14177        editor.newline(&Newline, window, cx);
14178        let (expected_text, expected_selections) = marked_text_ranges(
14179            indoc! {"
14180                aaaa
14181                bX
14182                ˇbbX
14183                b
14184                bX
14185                ˇbbX
14186                ˇb
14187                cccc"
14188            },
14189            false,
14190        );
14191        assert_eq!(editor.text(cx), expected_text);
14192        assert_eq!(editor.selections.ranges(cx), expected_selections);
14193    });
14194}
14195
14196#[gpui::test]
14197fn test_refresh_selections(cx: &mut TestAppContext) {
14198    init_test(cx, |_| {});
14199
14200    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14201    let mut excerpt1_id = None;
14202    let multibuffer = cx.new(|cx| {
14203        let mut multibuffer = MultiBuffer::new(ReadWrite);
14204        excerpt1_id = multibuffer
14205            .push_excerpts(
14206                buffer.clone(),
14207                [
14208                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14209                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14210                ],
14211                cx,
14212            )
14213            .into_iter()
14214            .next();
14215        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14216        multibuffer
14217    });
14218
14219    let editor = cx.add_window(|window, cx| {
14220        let mut editor = build_editor(multibuffer.clone(), window, cx);
14221        let snapshot = editor.snapshot(window, cx);
14222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14223            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14224        });
14225        editor.begin_selection(
14226            Point::new(2, 1).to_display_point(&snapshot),
14227            true,
14228            1,
14229            window,
14230            cx,
14231        );
14232        assert_eq!(
14233            editor.selections.ranges(cx),
14234            [
14235                Point::new(1, 3)..Point::new(1, 3),
14236                Point::new(2, 1)..Point::new(2, 1),
14237            ]
14238        );
14239        editor
14240    });
14241
14242    // Refreshing selections is a no-op when excerpts haven't changed.
14243    _ = editor.update(cx, |editor, window, cx| {
14244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14245        assert_eq!(
14246            editor.selections.ranges(cx),
14247            [
14248                Point::new(1, 3)..Point::new(1, 3),
14249                Point::new(2, 1)..Point::new(2, 1),
14250            ]
14251        );
14252    });
14253
14254    multibuffer.update(cx, |multibuffer, cx| {
14255        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14256    });
14257    _ = editor.update(cx, |editor, window, cx| {
14258        // Removing an excerpt causes the first selection to become degenerate.
14259        assert_eq!(
14260            editor.selections.ranges(cx),
14261            [
14262                Point::new(0, 0)..Point::new(0, 0),
14263                Point::new(0, 1)..Point::new(0, 1)
14264            ]
14265        );
14266
14267        // Refreshing selections will relocate the first selection to the original buffer
14268        // location.
14269        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14270        assert_eq!(
14271            editor.selections.ranges(cx),
14272            [
14273                Point::new(0, 1)..Point::new(0, 1),
14274                Point::new(0, 3)..Point::new(0, 3)
14275            ]
14276        );
14277        assert!(editor.selections.pending_anchor().is_some());
14278    });
14279}
14280
14281#[gpui::test]
14282fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14283    init_test(cx, |_| {});
14284
14285    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14286    let mut excerpt1_id = None;
14287    let multibuffer = cx.new(|cx| {
14288        let mut multibuffer = MultiBuffer::new(ReadWrite);
14289        excerpt1_id = multibuffer
14290            .push_excerpts(
14291                buffer.clone(),
14292                [
14293                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14294                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14295                ],
14296                cx,
14297            )
14298            .into_iter()
14299            .next();
14300        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14301        multibuffer
14302    });
14303
14304    let editor = cx.add_window(|window, cx| {
14305        let mut editor = build_editor(multibuffer.clone(), window, cx);
14306        let snapshot = editor.snapshot(window, cx);
14307        editor.begin_selection(
14308            Point::new(1, 3).to_display_point(&snapshot),
14309            false,
14310            1,
14311            window,
14312            cx,
14313        );
14314        assert_eq!(
14315            editor.selections.ranges(cx),
14316            [Point::new(1, 3)..Point::new(1, 3)]
14317        );
14318        editor
14319    });
14320
14321    multibuffer.update(cx, |multibuffer, cx| {
14322        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14323    });
14324    _ = editor.update(cx, |editor, window, cx| {
14325        assert_eq!(
14326            editor.selections.ranges(cx),
14327            [Point::new(0, 0)..Point::new(0, 0)]
14328        );
14329
14330        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14331        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14332        assert_eq!(
14333            editor.selections.ranges(cx),
14334            [Point::new(0, 3)..Point::new(0, 3)]
14335        );
14336        assert!(editor.selections.pending_anchor().is_some());
14337    });
14338}
14339
14340#[gpui::test]
14341async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14342    init_test(cx, |_| {});
14343
14344    let language = Arc::new(
14345        Language::new(
14346            LanguageConfig {
14347                brackets: BracketPairConfig {
14348                    pairs: vec![
14349                        BracketPair {
14350                            start: "{".to_string(),
14351                            end: "}".to_string(),
14352                            close: true,
14353                            surround: true,
14354                            newline: true,
14355                        },
14356                        BracketPair {
14357                            start: "/* ".to_string(),
14358                            end: " */".to_string(),
14359                            close: true,
14360                            surround: true,
14361                            newline: true,
14362                        },
14363                    ],
14364                    ..Default::default()
14365                },
14366                ..Default::default()
14367            },
14368            Some(tree_sitter_rust::LANGUAGE.into()),
14369        )
14370        .with_indents_query("")
14371        .unwrap(),
14372    );
14373
14374    let text = concat!(
14375        "{   }\n",     //
14376        "  x\n",       //
14377        "  /*   */\n", //
14378        "x\n",         //
14379        "{{} }\n",     //
14380    );
14381
14382    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14383    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14384    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14385    editor
14386        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14387        .await;
14388
14389    editor.update_in(cx, |editor, window, cx| {
14390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14391            s.select_display_ranges([
14392                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14393                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14394                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14395            ])
14396        });
14397        editor.newline(&Newline, window, cx);
14398
14399        assert_eq!(
14400            editor.buffer().read(cx).read(cx).text(),
14401            concat!(
14402                "{ \n",    // Suppress rustfmt
14403                "\n",      //
14404                "}\n",     //
14405                "  x\n",   //
14406                "  /* \n", //
14407                "  \n",    //
14408                "  */\n",  //
14409                "x\n",     //
14410                "{{} \n",  //
14411                "}\n",     //
14412            )
14413        );
14414    });
14415}
14416
14417#[gpui::test]
14418fn test_highlighted_ranges(cx: &mut TestAppContext) {
14419    init_test(cx, |_| {});
14420
14421    let editor = cx.add_window(|window, cx| {
14422        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14423        build_editor(buffer.clone(), window, cx)
14424    });
14425
14426    _ = editor.update(cx, |editor, window, cx| {
14427        struct Type1;
14428        struct Type2;
14429
14430        let buffer = editor.buffer.read(cx).snapshot(cx);
14431
14432        let anchor_range =
14433            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14434
14435        editor.highlight_background::<Type1>(
14436            &[
14437                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14438                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14439                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14440                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14441            ],
14442            |_| Hsla::red(),
14443            cx,
14444        );
14445        editor.highlight_background::<Type2>(
14446            &[
14447                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14448                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14449                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14450                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14451            ],
14452            |_| Hsla::green(),
14453            cx,
14454        );
14455
14456        let snapshot = editor.snapshot(window, cx);
14457        let mut highlighted_ranges = editor.background_highlights_in_range(
14458            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14459            &snapshot,
14460            cx.theme(),
14461        );
14462        // Enforce a consistent ordering based on color without relying on the ordering of the
14463        // highlight's `TypeId` which is non-executor.
14464        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14465        assert_eq!(
14466            highlighted_ranges,
14467            &[
14468                (
14469                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14470                    Hsla::red(),
14471                ),
14472                (
14473                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14474                    Hsla::red(),
14475                ),
14476                (
14477                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14478                    Hsla::green(),
14479                ),
14480                (
14481                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14482                    Hsla::green(),
14483                ),
14484            ]
14485        );
14486        assert_eq!(
14487            editor.background_highlights_in_range(
14488                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14489                &snapshot,
14490                cx.theme(),
14491            ),
14492            &[(
14493                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14494                Hsla::red(),
14495            )]
14496        );
14497    });
14498}
14499
14500#[gpui::test]
14501async fn test_following(cx: &mut TestAppContext) {
14502    init_test(cx, |_| {});
14503
14504    let fs = FakeFs::new(cx.executor());
14505    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14506
14507    let buffer = project.update(cx, |project, cx| {
14508        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14509        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14510    });
14511    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14512    let follower = cx.update(|cx| {
14513        cx.open_window(
14514            WindowOptions {
14515                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14516                    gpui::Point::new(px(0.), px(0.)),
14517                    gpui::Point::new(px(10.), px(80.)),
14518                ))),
14519                ..Default::default()
14520            },
14521            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14522        )
14523        .unwrap()
14524    });
14525
14526    let is_still_following = Rc::new(RefCell::new(true));
14527    let follower_edit_event_count = Rc::new(RefCell::new(0));
14528    let pending_update = Rc::new(RefCell::new(None));
14529    let leader_entity = leader.root(cx).unwrap();
14530    let follower_entity = follower.root(cx).unwrap();
14531    _ = follower.update(cx, {
14532        let update = pending_update.clone();
14533        let is_still_following = is_still_following.clone();
14534        let follower_edit_event_count = follower_edit_event_count.clone();
14535        |_, window, cx| {
14536            cx.subscribe_in(
14537                &leader_entity,
14538                window,
14539                move |_, leader, event, window, cx| {
14540                    leader.read(cx).add_event_to_update_proto(
14541                        event,
14542                        &mut update.borrow_mut(),
14543                        window,
14544                        cx,
14545                    );
14546                },
14547            )
14548            .detach();
14549
14550            cx.subscribe_in(
14551                &follower_entity,
14552                window,
14553                move |_, _, event: &EditorEvent, _window, _cx| {
14554                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14555                        *is_still_following.borrow_mut() = false;
14556                    }
14557
14558                    if let EditorEvent::BufferEdited = event {
14559                        *follower_edit_event_count.borrow_mut() += 1;
14560                    }
14561                },
14562            )
14563            .detach();
14564        }
14565    });
14566
14567    // Update the selections only
14568    _ = leader.update(cx, |leader, window, cx| {
14569        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14570            s.select_ranges([1..1])
14571        });
14572    });
14573    follower
14574        .update(cx, |follower, window, cx| {
14575            follower.apply_update_proto(
14576                &project,
14577                pending_update.borrow_mut().take().unwrap(),
14578                window,
14579                cx,
14580            )
14581        })
14582        .unwrap()
14583        .await
14584        .unwrap();
14585    _ = follower.update(cx, |follower, _, cx| {
14586        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14587    });
14588    assert!(*is_still_following.borrow());
14589    assert_eq!(*follower_edit_event_count.borrow(), 0);
14590
14591    // Update the scroll position only
14592    _ = leader.update(cx, |leader, window, cx| {
14593        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14594    });
14595    follower
14596        .update(cx, |follower, window, cx| {
14597            follower.apply_update_proto(
14598                &project,
14599                pending_update.borrow_mut().take().unwrap(),
14600                window,
14601                cx,
14602            )
14603        })
14604        .unwrap()
14605        .await
14606        .unwrap();
14607    assert_eq!(
14608        follower
14609            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14610            .unwrap(),
14611        gpui::Point::new(1.5, 3.5)
14612    );
14613    assert!(*is_still_following.borrow());
14614    assert_eq!(*follower_edit_event_count.borrow(), 0);
14615
14616    // Update the selections and scroll position. The follower's scroll position is updated
14617    // via autoscroll, not via the leader's exact scroll position.
14618    _ = leader.update(cx, |leader, window, cx| {
14619        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14620            s.select_ranges([0..0])
14621        });
14622        leader.request_autoscroll(Autoscroll::newest(), cx);
14623        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14624    });
14625    follower
14626        .update(cx, |follower, window, cx| {
14627            follower.apply_update_proto(
14628                &project,
14629                pending_update.borrow_mut().take().unwrap(),
14630                window,
14631                cx,
14632            )
14633        })
14634        .unwrap()
14635        .await
14636        .unwrap();
14637    _ = follower.update(cx, |follower, _, cx| {
14638        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14639        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14640    });
14641    assert!(*is_still_following.borrow());
14642
14643    // Creating a pending selection that precedes another selection
14644    _ = leader.update(cx, |leader, window, cx| {
14645        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14646            s.select_ranges([1..1])
14647        });
14648        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14649    });
14650    follower
14651        .update(cx, |follower, window, cx| {
14652            follower.apply_update_proto(
14653                &project,
14654                pending_update.borrow_mut().take().unwrap(),
14655                window,
14656                cx,
14657            )
14658        })
14659        .unwrap()
14660        .await
14661        .unwrap();
14662    _ = follower.update(cx, |follower, _, cx| {
14663        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14664    });
14665    assert!(*is_still_following.borrow());
14666
14667    // Extend the pending selection so that it surrounds another selection
14668    _ = leader.update(cx, |leader, window, cx| {
14669        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14670    });
14671    follower
14672        .update(cx, |follower, window, cx| {
14673            follower.apply_update_proto(
14674                &project,
14675                pending_update.borrow_mut().take().unwrap(),
14676                window,
14677                cx,
14678            )
14679        })
14680        .unwrap()
14681        .await
14682        .unwrap();
14683    _ = follower.update(cx, |follower, _, cx| {
14684        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14685    });
14686
14687    // Scrolling locally breaks the follow
14688    _ = follower.update(cx, |follower, window, cx| {
14689        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14690        follower.set_scroll_anchor(
14691            ScrollAnchor {
14692                anchor: top_anchor,
14693                offset: gpui::Point::new(0.0, 0.5),
14694            },
14695            window,
14696            cx,
14697        );
14698    });
14699    assert!(!(*is_still_following.borrow()));
14700}
14701
14702#[gpui::test]
14703async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14704    init_test(cx, |_| {});
14705
14706    let fs = FakeFs::new(cx.executor());
14707    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14708    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14709    let pane = workspace
14710        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14711        .unwrap();
14712
14713    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14714
14715    let leader = pane.update_in(cx, |_, window, cx| {
14716        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14717        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14718    });
14719
14720    // Start following the editor when it has no excerpts.
14721    let mut state_message =
14722        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14723    let workspace_entity = workspace.root(cx).unwrap();
14724    let follower_1 = cx
14725        .update_window(*workspace.deref(), |_, window, cx| {
14726            Editor::from_state_proto(
14727                workspace_entity,
14728                ViewId {
14729                    creator: CollaboratorId::PeerId(PeerId::default()),
14730                    id: 0,
14731                },
14732                &mut state_message,
14733                window,
14734                cx,
14735            )
14736        })
14737        .unwrap()
14738        .unwrap()
14739        .await
14740        .unwrap();
14741
14742    let update_message = Rc::new(RefCell::new(None));
14743    follower_1.update_in(cx, {
14744        let update = update_message.clone();
14745        |_, window, cx| {
14746            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14747                leader.read(cx).add_event_to_update_proto(
14748                    event,
14749                    &mut update.borrow_mut(),
14750                    window,
14751                    cx,
14752                );
14753            })
14754            .detach();
14755        }
14756    });
14757
14758    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14759        (
14760            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14761            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14762        )
14763    });
14764
14765    // Insert some excerpts.
14766    leader.update(cx, |leader, cx| {
14767        leader.buffer.update(cx, |multibuffer, cx| {
14768            multibuffer.set_excerpts_for_path(
14769                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14770                buffer_1.clone(),
14771                vec![
14772                    Point::row_range(0..3),
14773                    Point::row_range(1..6),
14774                    Point::row_range(12..15),
14775                ],
14776                0,
14777                cx,
14778            );
14779            multibuffer.set_excerpts_for_path(
14780                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14781                buffer_2.clone(),
14782                vec![Point::row_range(0..6), Point::row_range(8..12)],
14783                0,
14784                cx,
14785            );
14786        });
14787    });
14788
14789    // Apply the update of adding the excerpts.
14790    follower_1
14791        .update_in(cx, |follower, window, cx| {
14792            follower.apply_update_proto(
14793                &project,
14794                update_message.borrow().clone().unwrap(),
14795                window,
14796                cx,
14797            )
14798        })
14799        .await
14800        .unwrap();
14801    assert_eq!(
14802        follower_1.update(cx, |editor, cx| editor.text(cx)),
14803        leader.update(cx, |editor, cx| editor.text(cx))
14804    );
14805    update_message.borrow_mut().take();
14806
14807    // Start following separately after it already has excerpts.
14808    let mut state_message =
14809        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14810    let workspace_entity = workspace.root(cx).unwrap();
14811    let follower_2 = cx
14812        .update_window(*workspace.deref(), |_, window, cx| {
14813            Editor::from_state_proto(
14814                workspace_entity,
14815                ViewId {
14816                    creator: CollaboratorId::PeerId(PeerId::default()),
14817                    id: 0,
14818                },
14819                &mut state_message,
14820                window,
14821                cx,
14822            )
14823        })
14824        .unwrap()
14825        .unwrap()
14826        .await
14827        .unwrap();
14828    assert_eq!(
14829        follower_2.update(cx, |editor, cx| editor.text(cx)),
14830        leader.update(cx, |editor, cx| editor.text(cx))
14831    );
14832
14833    // Remove some excerpts.
14834    leader.update(cx, |leader, cx| {
14835        leader.buffer.update(cx, |multibuffer, cx| {
14836            let excerpt_ids = multibuffer.excerpt_ids();
14837            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14838            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14839        });
14840    });
14841
14842    // Apply the update of removing the excerpts.
14843    follower_1
14844        .update_in(cx, |follower, window, cx| {
14845            follower.apply_update_proto(
14846                &project,
14847                update_message.borrow().clone().unwrap(),
14848                window,
14849                cx,
14850            )
14851        })
14852        .await
14853        .unwrap();
14854    follower_2
14855        .update_in(cx, |follower, window, cx| {
14856            follower.apply_update_proto(
14857                &project,
14858                update_message.borrow().clone().unwrap(),
14859                window,
14860                cx,
14861            )
14862        })
14863        .await
14864        .unwrap();
14865    update_message.borrow_mut().take();
14866    assert_eq!(
14867        follower_1.update(cx, |editor, cx| editor.text(cx)),
14868        leader.update(cx, |editor, cx| editor.text(cx))
14869    );
14870}
14871
14872#[gpui::test]
14873async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14874    init_test(cx, |_| {});
14875
14876    let mut cx = EditorTestContext::new(cx).await;
14877    let lsp_store =
14878        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14879
14880    cx.set_state(indoc! {"
14881        ˇfn func(abc def: i32) -> u32 {
14882        }
14883    "});
14884
14885    cx.update(|_, cx| {
14886        lsp_store.update(cx, |lsp_store, cx| {
14887            lsp_store
14888                .update_diagnostics(
14889                    LanguageServerId(0),
14890                    lsp::PublishDiagnosticsParams {
14891                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14892                        version: None,
14893                        diagnostics: vec![
14894                            lsp::Diagnostic {
14895                                range: lsp::Range::new(
14896                                    lsp::Position::new(0, 11),
14897                                    lsp::Position::new(0, 12),
14898                                ),
14899                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14900                                ..Default::default()
14901                            },
14902                            lsp::Diagnostic {
14903                                range: lsp::Range::new(
14904                                    lsp::Position::new(0, 12),
14905                                    lsp::Position::new(0, 15),
14906                                ),
14907                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14908                                ..Default::default()
14909                            },
14910                            lsp::Diagnostic {
14911                                range: lsp::Range::new(
14912                                    lsp::Position::new(0, 25),
14913                                    lsp::Position::new(0, 28),
14914                                ),
14915                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14916                                ..Default::default()
14917                            },
14918                        ],
14919                    },
14920                    None,
14921                    DiagnosticSourceKind::Pushed,
14922                    &[],
14923                    cx,
14924                )
14925                .unwrap()
14926        });
14927    });
14928
14929    executor.run_until_parked();
14930
14931    cx.update_editor(|editor, window, cx| {
14932        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14933    });
14934
14935    cx.assert_editor_state(indoc! {"
14936        fn func(abc def: i32) -> ˇu32 {
14937        }
14938    "});
14939
14940    cx.update_editor(|editor, window, cx| {
14941        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14942    });
14943
14944    cx.assert_editor_state(indoc! {"
14945        fn func(abc ˇdef: i32) -> u32 {
14946        }
14947    "});
14948
14949    cx.update_editor(|editor, window, cx| {
14950        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14951    });
14952
14953    cx.assert_editor_state(indoc! {"
14954        fn func(abcˇ def: i32) -> u32 {
14955        }
14956    "});
14957
14958    cx.update_editor(|editor, window, cx| {
14959        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14960    });
14961
14962    cx.assert_editor_state(indoc! {"
14963        fn func(abc def: i32) -> ˇu32 {
14964        }
14965    "});
14966}
14967
14968#[gpui::test]
14969async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14970    init_test(cx, |_| {});
14971
14972    let mut cx = EditorTestContext::new(cx).await;
14973
14974    let diff_base = r#"
14975        use some::mod;
14976
14977        const A: u32 = 42;
14978
14979        fn main() {
14980            println!("hello");
14981
14982            println!("world");
14983        }
14984        "#
14985    .unindent();
14986
14987    // Edits are modified, removed, modified, added
14988    cx.set_state(
14989        &r#"
14990        use some::modified;
14991
14992        ˇ
14993        fn main() {
14994            println!("hello there");
14995
14996            println!("around the");
14997            println!("world");
14998        }
14999        "#
15000        .unindent(),
15001    );
15002
15003    cx.set_head_text(&diff_base);
15004    executor.run_until_parked();
15005
15006    cx.update_editor(|editor, window, cx| {
15007        //Wrap around the bottom of the buffer
15008        for _ in 0..3 {
15009            editor.go_to_next_hunk(&GoToHunk, window, cx);
15010        }
15011    });
15012
15013    cx.assert_editor_state(
15014        &r#"
15015        ˇuse some::modified;
15016
15017
15018        fn main() {
15019            println!("hello there");
15020
15021            println!("around the");
15022            println!("world");
15023        }
15024        "#
15025        .unindent(),
15026    );
15027
15028    cx.update_editor(|editor, window, cx| {
15029        //Wrap around the top of the buffer
15030        for _ in 0..2 {
15031            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15032        }
15033    });
15034
15035    cx.assert_editor_state(
15036        &r#"
15037        use some::modified;
15038
15039
15040        fn main() {
15041        ˇ    println!("hello there");
15042
15043            println!("around the");
15044            println!("world");
15045        }
15046        "#
15047        .unindent(),
15048    );
15049
15050    cx.update_editor(|editor, window, cx| {
15051        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15052    });
15053
15054    cx.assert_editor_state(
15055        &r#"
15056        use some::modified;
15057
15058        ˇ
15059        fn main() {
15060            println!("hello there");
15061
15062            println!("around the");
15063            println!("world");
15064        }
15065        "#
15066        .unindent(),
15067    );
15068
15069    cx.update_editor(|editor, window, cx| {
15070        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15071    });
15072
15073    cx.assert_editor_state(
15074        &r#"
15075        ˇuse some::modified;
15076
15077
15078        fn main() {
15079            println!("hello there");
15080
15081            println!("around the");
15082            println!("world");
15083        }
15084        "#
15085        .unindent(),
15086    );
15087
15088    cx.update_editor(|editor, window, cx| {
15089        for _ in 0..2 {
15090            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15091        }
15092    });
15093
15094    cx.assert_editor_state(
15095        &r#"
15096        use some::modified;
15097
15098
15099        fn main() {
15100        ˇ    println!("hello there");
15101
15102            println!("around the");
15103            println!("world");
15104        }
15105        "#
15106        .unindent(),
15107    );
15108
15109    cx.update_editor(|editor, window, cx| {
15110        editor.fold(&Fold, window, cx);
15111    });
15112
15113    cx.update_editor(|editor, window, cx| {
15114        editor.go_to_next_hunk(&GoToHunk, window, cx);
15115    });
15116
15117    cx.assert_editor_state(
15118        &r#"
15119        ˇuse some::modified;
15120
15121
15122        fn main() {
15123            println!("hello there");
15124
15125            println!("around the");
15126            println!("world");
15127        }
15128        "#
15129        .unindent(),
15130    );
15131}
15132
15133#[test]
15134fn test_split_words() {
15135    fn split(text: &str) -> Vec<&str> {
15136        split_words(text).collect()
15137    }
15138
15139    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15140    assert_eq!(split("hello_world"), &["hello_", "world"]);
15141    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15142    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15143    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15144    assert_eq!(split("helloworld"), &["helloworld"]);
15145
15146    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15147}
15148
15149#[gpui::test]
15150async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15151    init_test(cx, |_| {});
15152
15153    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15154    let mut assert = |before, after| {
15155        let _state_context = cx.set_state(before);
15156        cx.run_until_parked();
15157        cx.update_editor(|editor, window, cx| {
15158            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15159        });
15160        cx.run_until_parked();
15161        cx.assert_editor_state(after);
15162    };
15163
15164    // Outside bracket jumps to outside of matching bracket
15165    assert("console.logˇ(var);", "console.log(var)ˇ;");
15166    assert("console.log(var)ˇ;", "console.logˇ(var);");
15167
15168    // Inside bracket jumps to inside of matching bracket
15169    assert("console.log(ˇvar);", "console.log(varˇ);");
15170    assert("console.log(varˇ);", "console.log(ˇvar);");
15171
15172    // When outside a bracket and inside, favor jumping to the inside bracket
15173    assert(
15174        "console.log('foo', [1, 2, 3]ˇ);",
15175        "console.log(ˇ'foo', [1, 2, 3]);",
15176    );
15177    assert(
15178        "console.log(ˇ'foo', [1, 2, 3]);",
15179        "console.log('foo', [1, 2, 3]ˇ);",
15180    );
15181
15182    // Bias forward if two options are equally likely
15183    assert(
15184        "let result = curried_fun()ˇ();",
15185        "let result = curried_fun()()ˇ;",
15186    );
15187
15188    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15189    assert(
15190        indoc! {"
15191            function test() {
15192                console.log('test')ˇ
15193            }"},
15194        indoc! {"
15195            function test() {
15196                console.logˇ('test')
15197            }"},
15198    );
15199}
15200
15201#[gpui::test]
15202async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15203    init_test(cx, |_| {});
15204
15205    let fs = FakeFs::new(cx.executor());
15206    fs.insert_tree(
15207        path!("/a"),
15208        json!({
15209            "main.rs": "fn main() { let a = 5; }",
15210            "other.rs": "// Test file",
15211        }),
15212    )
15213    .await;
15214    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15215
15216    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15217    language_registry.add(Arc::new(Language::new(
15218        LanguageConfig {
15219            name: "Rust".into(),
15220            matcher: LanguageMatcher {
15221                path_suffixes: vec!["rs".to_string()],
15222                ..Default::default()
15223            },
15224            brackets: BracketPairConfig {
15225                pairs: vec![BracketPair {
15226                    start: "{".to_string(),
15227                    end: "}".to_string(),
15228                    close: true,
15229                    surround: true,
15230                    newline: true,
15231                }],
15232                disabled_scopes_by_bracket_ix: Vec::new(),
15233            },
15234            ..Default::default()
15235        },
15236        Some(tree_sitter_rust::LANGUAGE.into()),
15237    )));
15238    let mut fake_servers = language_registry.register_fake_lsp(
15239        "Rust",
15240        FakeLspAdapter {
15241            capabilities: lsp::ServerCapabilities {
15242                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15243                    first_trigger_character: "{".to_string(),
15244                    more_trigger_character: None,
15245                }),
15246                ..Default::default()
15247            },
15248            ..Default::default()
15249        },
15250    );
15251
15252    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15253
15254    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15255
15256    let worktree_id = workspace
15257        .update(cx, |workspace, _, cx| {
15258            workspace.project().update(cx, |project, cx| {
15259                project.worktrees(cx).next().unwrap().read(cx).id()
15260            })
15261        })
15262        .unwrap();
15263
15264    let buffer = project
15265        .update(cx, |project, cx| {
15266            project.open_local_buffer(path!("/a/main.rs"), cx)
15267        })
15268        .await
15269        .unwrap();
15270    let editor_handle = workspace
15271        .update(cx, |workspace, window, cx| {
15272            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15273        })
15274        .unwrap()
15275        .await
15276        .unwrap()
15277        .downcast::<Editor>()
15278        .unwrap();
15279
15280    cx.executor().start_waiting();
15281    let fake_server = fake_servers.next().await.unwrap();
15282
15283    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15284        |params, _| async move {
15285            assert_eq!(
15286                params.text_document_position.text_document.uri,
15287                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15288            );
15289            assert_eq!(
15290                params.text_document_position.position,
15291                lsp::Position::new(0, 21),
15292            );
15293
15294            Ok(Some(vec![lsp::TextEdit {
15295                new_text: "]".to_string(),
15296                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15297            }]))
15298        },
15299    );
15300
15301    editor_handle.update_in(cx, |editor, window, cx| {
15302        window.focus(&editor.focus_handle(cx));
15303        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15304            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15305        });
15306        editor.handle_input("{", window, cx);
15307    });
15308
15309    cx.executor().run_until_parked();
15310
15311    buffer.update(cx, |buffer, _| {
15312        assert_eq!(
15313            buffer.text(),
15314            "fn main() { let a = {5}; }",
15315            "No extra braces from on type formatting should appear in the buffer"
15316        )
15317    });
15318}
15319
15320#[gpui::test(iterations = 20, seeds(31))]
15321async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15322    init_test(cx, |_| {});
15323
15324    let mut cx = EditorLspTestContext::new_rust(
15325        lsp::ServerCapabilities {
15326            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15327                first_trigger_character: ".".to_string(),
15328                more_trigger_character: None,
15329            }),
15330            ..Default::default()
15331        },
15332        cx,
15333    )
15334    .await;
15335
15336    cx.update_buffer(|buffer, _| {
15337        // This causes autoindent to be async.
15338        buffer.set_sync_parse_timeout(Duration::ZERO)
15339    });
15340
15341    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15342    cx.simulate_keystroke("\n");
15343    cx.run_until_parked();
15344
15345    let buffer_cloned =
15346        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15347    let mut request =
15348        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15349            let buffer_cloned = buffer_cloned.clone();
15350            async move {
15351                buffer_cloned.update(&mut cx, |buffer, _| {
15352                    assert_eq!(
15353                        buffer.text(),
15354                        "fn c() {\n    d()\n        .\n}\n",
15355                        "OnTypeFormatting should triggered after autoindent applied"
15356                    )
15357                })?;
15358
15359                Ok(Some(vec![]))
15360            }
15361        });
15362
15363    cx.simulate_keystroke(".");
15364    cx.run_until_parked();
15365
15366    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15367    assert!(request.next().await.is_some());
15368    request.close();
15369    assert!(request.next().await.is_none());
15370}
15371
15372#[gpui::test]
15373async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15374    init_test(cx, |_| {});
15375
15376    let fs = FakeFs::new(cx.executor());
15377    fs.insert_tree(
15378        path!("/a"),
15379        json!({
15380            "main.rs": "fn main() { let a = 5; }",
15381            "other.rs": "// Test file",
15382        }),
15383    )
15384    .await;
15385
15386    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15387
15388    let server_restarts = Arc::new(AtomicUsize::new(0));
15389    let closure_restarts = Arc::clone(&server_restarts);
15390    let language_server_name = "test language server";
15391    let language_name: LanguageName = "Rust".into();
15392
15393    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15394    language_registry.add(Arc::new(Language::new(
15395        LanguageConfig {
15396            name: language_name.clone(),
15397            matcher: LanguageMatcher {
15398                path_suffixes: vec!["rs".to_string()],
15399                ..Default::default()
15400            },
15401            ..Default::default()
15402        },
15403        Some(tree_sitter_rust::LANGUAGE.into()),
15404    )));
15405    let mut fake_servers = language_registry.register_fake_lsp(
15406        "Rust",
15407        FakeLspAdapter {
15408            name: language_server_name,
15409            initialization_options: Some(json!({
15410                "testOptionValue": true
15411            })),
15412            initializer: Some(Box::new(move |fake_server| {
15413                let task_restarts = Arc::clone(&closure_restarts);
15414                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15415                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15416                    futures::future::ready(Ok(()))
15417                });
15418            })),
15419            ..Default::default()
15420        },
15421    );
15422
15423    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15424    let _buffer = project
15425        .update(cx, |project, cx| {
15426            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15427        })
15428        .await
15429        .unwrap();
15430    let _fake_server = fake_servers.next().await.unwrap();
15431    update_test_language_settings(cx, |language_settings| {
15432        language_settings.languages.0.insert(
15433            language_name.clone(),
15434            LanguageSettingsContent {
15435                tab_size: NonZeroU32::new(8),
15436                ..Default::default()
15437            },
15438        );
15439    });
15440    cx.executor().run_until_parked();
15441    assert_eq!(
15442        server_restarts.load(atomic::Ordering::Acquire),
15443        0,
15444        "Should not restart LSP server on an unrelated change"
15445    );
15446
15447    update_test_project_settings(cx, |project_settings| {
15448        project_settings.lsp.insert(
15449            "Some other server name".into(),
15450            LspSettings {
15451                binary: None,
15452                settings: None,
15453                initialization_options: Some(json!({
15454                    "some other init value": false
15455                })),
15456                enable_lsp_tasks: false,
15457            },
15458        );
15459    });
15460    cx.executor().run_until_parked();
15461    assert_eq!(
15462        server_restarts.load(atomic::Ordering::Acquire),
15463        0,
15464        "Should not restart LSP server on an unrelated LSP settings change"
15465    );
15466
15467    update_test_project_settings(cx, |project_settings| {
15468        project_settings.lsp.insert(
15469            language_server_name.into(),
15470            LspSettings {
15471                binary: None,
15472                settings: None,
15473                initialization_options: Some(json!({
15474                    "anotherInitValue": false
15475                })),
15476                enable_lsp_tasks: false,
15477            },
15478        );
15479    });
15480    cx.executor().run_until_parked();
15481    assert_eq!(
15482        server_restarts.load(atomic::Ordering::Acquire),
15483        1,
15484        "Should restart LSP server on a related LSP settings change"
15485    );
15486
15487    update_test_project_settings(cx, |project_settings| {
15488        project_settings.lsp.insert(
15489            language_server_name.into(),
15490            LspSettings {
15491                binary: None,
15492                settings: None,
15493                initialization_options: Some(json!({
15494                    "anotherInitValue": false
15495                })),
15496                enable_lsp_tasks: false,
15497            },
15498        );
15499    });
15500    cx.executor().run_until_parked();
15501    assert_eq!(
15502        server_restarts.load(atomic::Ordering::Acquire),
15503        1,
15504        "Should not restart LSP server on a related LSP settings change that is the same"
15505    );
15506
15507    update_test_project_settings(cx, |project_settings| {
15508        project_settings.lsp.insert(
15509            language_server_name.into(),
15510            LspSettings {
15511                binary: None,
15512                settings: None,
15513                initialization_options: None,
15514                enable_lsp_tasks: false,
15515            },
15516        );
15517    });
15518    cx.executor().run_until_parked();
15519    assert_eq!(
15520        server_restarts.load(atomic::Ordering::Acquire),
15521        2,
15522        "Should restart LSP server on another related LSP settings change"
15523    );
15524}
15525
15526#[gpui::test]
15527async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15528    init_test(cx, |_| {});
15529
15530    let mut cx = EditorLspTestContext::new_rust(
15531        lsp::ServerCapabilities {
15532            completion_provider: Some(lsp::CompletionOptions {
15533                trigger_characters: Some(vec![".".to_string()]),
15534                resolve_provider: Some(true),
15535                ..Default::default()
15536            }),
15537            ..Default::default()
15538        },
15539        cx,
15540    )
15541    .await;
15542
15543    cx.set_state("fn main() { let a = 2ˇ; }");
15544    cx.simulate_keystroke(".");
15545    let completion_item = lsp::CompletionItem {
15546        label: "some".into(),
15547        kind: Some(lsp::CompletionItemKind::SNIPPET),
15548        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15549        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15550            kind: lsp::MarkupKind::Markdown,
15551            value: "```rust\nSome(2)\n```".to_string(),
15552        })),
15553        deprecated: Some(false),
15554        sort_text: Some("fffffff2".to_string()),
15555        filter_text: Some("some".to_string()),
15556        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15557        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15558            range: lsp::Range {
15559                start: lsp::Position {
15560                    line: 0,
15561                    character: 22,
15562                },
15563                end: lsp::Position {
15564                    line: 0,
15565                    character: 22,
15566                },
15567            },
15568            new_text: "Some(2)".to_string(),
15569        })),
15570        additional_text_edits: Some(vec![lsp::TextEdit {
15571            range: lsp::Range {
15572                start: lsp::Position {
15573                    line: 0,
15574                    character: 20,
15575                },
15576                end: lsp::Position {
15577                    line: 0,
15578                    character: 22,
15579                },
15580            },
15581            new_text: "".to_string(),
15582        }]),
15583        ..Default::default()
15584    };
15585
15586    let closure_completion_item = completion_item.clone();
15587    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15588        let task_completion_item = closure_completion_item.clone();
15589        async move {
15590            Ok(Some(lsp::CompletionResponse::Array(vec![
15591                task_completion_item,
15592            ])))
15593        }
15594    });
15595
15596    request.next().await;
15597
15598    cx.condition(|editor, _| editor.context_menu_visible())
15599        .await;
15600    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15601        editor
15602            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15603            .unwrap()
15604    });
15605    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15606
15607    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15608        let task_completion_item = completion_item.clone();
15609        async move { Ok(task_completion_item) }
15610    })
15611    .next()
15612    .await
15613    .unwrap();
15614    apply_additional_edits.await.unwrap();
15615    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15616}
15617
15618#[gpui::test]
15619async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15620    init_test(cx, |_| {});
15621
15622    let mut cx = EditorLspTestContext::new_rust(
15623        lsp::ServerCapabilities {
15624            completion_provider: Some(lsp::CompletionOptions {
15625                trigger_characters: Some(vec![".".to_string()]),
15626                resolve_provider: Some(true),
15627                ..Default::default()
15628            }),
15629            ..Default::default()
15630        },
15631        cx,
15632    )
15633    .await;
15634
15635    cx.set_state("fn main() { let a = 2ˇ; }");
15636    cx.simulate_keystroke(".");
15637
15638    let item1 = lsp::CompletionItem {
15639        label: "method id()".to_string(),
15640        filter_text: Some("id".to_string()),
15641        detail: None,
15642        documentation: None,
15643        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15644            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15645            new_text: ".id".to_string(),
15646        })),
15647        ..lsp::CompletionItem::default()
15648    };
15649
15650    let item2 = lsp::CompletionItem {
15651        label: "other".to_string(),
15652        filter_text: Some("other".to_string()),
15653        detail: None,
15654        documentation: None,
15655        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15656            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15657            new_text: ".other".to_string(),
15658        })),
15659        ..lsp::CompletionItem::default()
15660    };
15661
15662    let item1 = item1.clone();
15663    cx.set_request_handler::<lsp::request::Completion, _, _>({
15664        let item1 = item1.clone();
15665        move |_, _, _| {
15666            let item1 = item1.clone();
15667            let item2 = item2.clone();
15668            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15669        }
15670    })
15671    .next()
15672    .await;
15673
15674    cx.condition(|editor, _| editor.context_menu_visible())
15675        .await;
15676    cx.update_editor(|editor, _, _| {
15677        let context_menu = editor.context_menu.borrow_mut();
15678        let context_menu = context_menu
15679            .as_ref()
15680            .expect("Should have the context menu deployed");
15681        match context_menu {
15682            CodeContextMenu::Completions(completions_menu) => {
15683                let completions = completions_menu.completions.borrow_mut();
15684                assert_eq!(
15685                    completions
15686                        .iter()
15687                        .map(|completion| &completion.label.text)
15688                        .collect::<Vec<_>>(),
15689                    vec!["method id()", "other"]
15690                )
15691            }
15692            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15693        }
15694    });
15695
15696    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15697        let item1 = item1.clone();
15698        move |_, item_to_resolve, _| {
15699            let item1 = item1.clone();
15700            async move {
15701                if item1 == item_to_resolve {
15702                    Ok(lsp::CompletionItem {
15703                        label: "method id()".to_string(),
15704                        filter_text: Some("id".to_string()),
15705                        detail: Some("Now resolved!".to_string()),
15706                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15707                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15708                            range: lsp::Range::new(
15709                                lsp::Position::new(0, 22),
15710                                lsp::Position::new(0, 22),
15711                            ),
15712                            new_text: ".id".to_string(),
15713                        })),
15714                        ..lsp::CompletionItem::default()
15715                    })
15716                } else {
15717                    Ok(item_to_resolve)
15718                }
15719            }
15720        }
15721    })
15722    .next()
15723    .await
15724    .unwrap();
15725    cx.run_until_parked();
15726
15727    cx.update_editor(|editor, window, cx| {
15728        editor.context_menu_next(&Default::default(), window, cx);
15729    });
15730
15731    cx.update_editor(|editor, _, _| {
15732        let context_menu = editor.context_menu.borrow_mut();
15733        let context_menu = context_menu
15734            .as_ref()
15735            .expect("Should have the context menu deployed");
15736        match context_menu {
15737            CodeContextMenu::Completions(completions_menu) => {
15738                let completions = completions_menu.completions.borrow_mut();
15739                assert_eq!(
15740                    completions
15741                        .iter()
15742                        .map(|completion| &completion.label.text)
15743                        .collect::<Vec<_>>(),
15744                    vec!["method id() Now resolved!", "other"],
15745                    "Should update first completion label, but not second as the filter text did not match."
15746                );
15747            }
15748            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15749        }
15750    });
15751}
15752
15753#[gpui::test]
15754async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15755    init_test(cx, |_| {});
15756    let mut cx = EditorLspTestContext::new_rust(
15757        lsp::ServerCapabilities {
15758            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15759            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15760            completion_provider: Some(lsp::CompletionOptions {
15761                resolve_provider: Some(true),
15762                ..Default::default()
15763            }),
15764            ..Default::default()
15765        },
15766        cx,
15767    )
15768    .await;
15769    cx.set_state(indoc! {"
15770        struct TestStruct {
15771            field: i32
15772        }
15773
15774        fn mainˇ() {
15775            let unused_var = 42;
15776            let test_struct = TestStruct { field: 42 };
15777        }
15778    "});
15779    let symbol_range = cx.lsp_range(indoc! {"
15780        struct TestStruct {
15781            field: i32
15782        }
15783
15784        «fn main»() {
15785            let unused_var = 42;
15786            let test_struct = TestStruct { field: 42 };
15787        }
15788    "});
15789    let mut hover_requests =
15790        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15791            Ok(Some(lsp::Hover {
15792                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15793                    kind: lsp::MarkupKind::Markdown,
15794                    value: "Function documentation".to_string(),
15795                }),
15796                range: Some(symbol_range),
15797            }))
15798        });
15799
15800    // Case 1: Test that code action menu hide hover popover
15801    cx.dispatch_action(Hover);
15802    hover_requests.next().await;
15803    cx.condition(|editor, _| editor.hover_state.visible()).await;
15804    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15805        move |_, _, _| async move {
15806            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15807                lsp::CodeAction {
15808                    title: "Remove unused variable".to_string(),
15809                    kind: Some(CodeActionKind::QUICKFIX),
15810                    edit: Some(lsp::WorkspaceEdit {
15811                        changes: Some(
15812                            [(
15813                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15814                                vec![lsp::TextEdit {
15815                                    range: lsp::Range::new(
15816                                        lsp::Position::new(5, 4),
15817                                        lsp::Position::new(5, 27),
15818                                    ),
15819                                    new_text: "".to_string(),
15820                                }],
15821                            )]
15822                            .into_iter()
15823                            .collect(),
15824                        ),
15825                        ..Default::default()
15826                    }),
15827                    ..Default::default()
15828                },
15829            )]))
15830        },
15831    );
15832    cx.update_editor(|editor, window, cx| {
15833        editor.toggle_code_actions(
15834            &ToggleCodeActions {
15835                deployed_from: None,
15836                quick_launch: false,
15837            },
15838            window,
15839            cx,
15840        );
15841    });
15842    code_action_requests.next().await;
15843    cx.run_until_parked();
15844    cx.condition(|editor, _| editor.context_menu_visible())
15845        .await;
15846    cx.update_editor(|editor, _, _| {
15847        assert!(
15848            !editor.hover_state.visible(),
15849            "Hover popover should be hidden when code action menu is shown"
15850        );
15851        // Hide code actions
15852        editor.context_menu.take();
15853    });
15854
15855    // Case 2: Test that code completions hide hover popover
15856    cx.dispatch_action(Hover);
15857    hover_requests.next().await;
15858    cx.condition(|editor, _| editor.hover_state.visible()).await;
15859    let counter = Arc::new(AtomicUsize::new(0));
15860    let mut completion_requests =
15861        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15862            let counter = counter.clone();
15863            async move {
15864                counter.fetch_add(1, atomic::Ordering::Release);
15865                Ok(Some(lsp::CompletionResponse::Array(vec![
15866                    lsp::CompletionItem {
15867                        label: "main".into(),
15868                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15869                        detail: Some("() -> ()".to_string()),
15870                        ..Default::default()
15871                    },
15872                    lsp::CompletionItem {
15873                        label: "TestStruct".into(),
15874                        kind: Some(lsp::CompletionItemKind::STRUCT),
15875                        detail: Some("struct TestStruct".to_string()),
15876                        ..Default::default()
15877                    },
15878                ])))
15879            }
15880        });
15881    cx.update_editor(|editor, window, cx| {
15882        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15883    });
15884    completion_requests.next().await;
15885    cx.condition(|editor, _| editor.context_menu_visible())
15886        .await;
15887    cx.update_editor(|editor, _, _| {
15888        assert!(
15889            !editor.hover_state.visible(),
15890            "Hover popover should be hidden when completion menu is shown"
15891        );
15892    });
15893}
15894
15895#[gpui::test]
15896async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15897    init_test(cx, |_| {});
15898
15899    let mut cx = EditorLspTestContext::new_rust(
15900        lsp::ServerCapabilities {
15901            completion_provider: Some(lsp::CompletionOptions {
15902                trigger_characters: Some(vec![".".to_string()]),
15903                resolve_provider: Some(true),
15904                ..Default::default()
15905            }),
15906            ..Default::default()
15907        },
15908        cx,
15909    )
15910    .await;
15911
15912    cx.set_state("fn main() { let a = 2ˇ; }");
15913    cx.simulate_keystroke(".");
15914
15915    let unresolved_item_1 = lsp::CompletionItem {
15916        label: "id".to_string(),
15917        filter_text: Some("id".to_string()),
15918        detail: None,
15919        documentation: None,
15920        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15921            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15922            new_text: ".id".to_string(),
15923        })),
15924        ..lsp::CompletionItem::default()
15925    };
15926    let resolved_item_1 = lsp::CompletionItem {
15927        additional_text_edits: Some(vec![lsp::TextEdit {
15928            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15929            new_text: "!!".to_string(),
15930        }]),
15931        ..unresolved_item_1.clone()
15932    };
15933    let unresolved_item_2 = lsp::CompletionItem {
15934        label: "other".to_string(),
15935        filter_text: Some("other".to_string()),
15936        detail: None,
15937        documentation: None,
15938        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15939            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15940            new_text: ".other".to_string(),
15941        })),
15942        ..lsp::CompletionItem::default()
15943    };
15944    let resolved_item_2 = lsp::CompletionItem {
15945        additional_text_edits: Some(vec![lsp::TextEdit {
15946            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15947            new_text: "??".to_string(),
15948        }]),
15949        ..unresolved_item_2.clone()
15950    };
15951
15952    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15953    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15954    cx.lsp
15955        .server
15956        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15957            let unresolved_item_1 = unresolved_item_1.clone();
15958            let resolved_item_1 = resolved_item_1.clone();
15959            let unresolved_item_2 = unresolved_item_2.clone();
15960            let resolved_item_2 = resolved_item_2.clone();
15961            let resolve_requests_1 = resolve_requests_1.clone();
15962            let resolve_requests_2 = resolve_requests_2.clone();
15963            move |unresolved_request, _| {
15964                let unresolved_item_1 = unresolved_item_1.clone();
15965                let resolved_item_1 = resolved_item_1.clone();
15966                let unresolved_item_2 = unresolved_item_2.clone();
15967                let resolved_item_2 = resolved_item_2.clone();
15968                let resolve_requests_1 = resolve_requests_1.clone();
15969                let resolve_requests_2 = resolve_requests_2.clone();
15970                async move {
15971                    if unresolved_request == unresolved_item_1 {
15972                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15973                        Ok(resolved_item_1.clone())
15974                    } else if unresolved_request == unresolved_item_2 {
15975                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15976                        Ok(resolved_item_2.clone())
15977                    } else {
15978                        panic!("Unexpected completion item {unresolved_request:?}")
15979                    }
15980                }
15981            }
15982        })
15983        .detach();
15984
15985    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15986        let unresolved_item_1 = unresolved_item_1.clone();
15987        let unresolved_item_2 = unresolved_item_2.clone();
15988        async move {
15989            Ok(Some(lsp::CompletionResponse::Array(vec![
15990                unresolved_item_1,
15991                unresolved_item_2,
15992            ])))
15993        }
15994    })
15995    .next()
15996    .await;
15997
15998    cx.condition(|editor, _| editor.context_menu_visible())
15999        .await;
16000    cx.update_editor(|editor, _, _| {
16001        let context_menu = editor.context_menu.borrow_mut();
16002        let context_menu = context_menu
16003            .as_ref()
16004            .expect("Should have the context menu deployed");
16005        match context_menu {
16006            CodeContextMenu::Completions(completions_menu) => {
16007                let completions = completions_menu.completions.borrow_mut();
16008                assert_eq!(
16009                    completions
16010                        .iter()
16011                        .map(|completion| &completion.label.text)
16012                        .collect::<Vec<_>>(),
16013                    vec!["id", "other"]
16014                )
16015            }
16016            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16017        }
16018    });
16019    cx.run_until_parked();
16020
16021    cx.update_editor(|editor, window, cx| {
16022        editor.context_menu_next(&ContextMenuNext, window, cx);
16023    });
16024    cx.run_until_parked();
16025    cx.update_editor(|editor, window, cx| {
16026        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16027    });
16028    cx.run_until_parked();
16029    cx.update_editor(|editor, window, cx| {
16030        editor.context_menu_next(&ContextMenuNext, window, cx);
16031    });
16032    cx.run_until_parked();
16033    cx.update_editor(|editor, window, cx| {
16034        editor
16035            .compose_completion(&ComposeCompletion::default(), window, cx)
16036            .expect("No task returned")
16037    })
16038    .await
16039    .expect("Completion failed");
16040    cx.run_until_parked();
16041
16042    cx.update_editor(|editor, _, cx| {
16043        assert_eq!(
16044            resolve_requests_1.load(atomic::Ordering::Acquire),
16045            1,
16046            "Should always resolve once despite multiple selections"
16047        );
16048        assert_eq!(
16049            resolve_requests_2.load(atomic::Ordering::Acquire),
16050            1,
16051            "Should always resolve once after multiple selections and applying the completion"
16052        );
16053        assert_eq!(
16054            editor.text(cx),
16055            "fn main() { let a = ??.other; }",
16056            "Should use resolved data when applying the completion"
16057        );
16058    });
16059}
16060
16061#[gpui::test]
16062async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16063    init_test(cx, |_| {});
16064
16065    let item_0 = lsp::CompletionItem {
16066        label: "abs".into(),
16067        insert_text: Some("abs".into()),
16068        data: Some(json!({ "very": "special"})),
16069        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16070        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16071            lsp::InsertReplaceEdit {
16072                new_text: "abs".to_string(),
16073                insert: lsp::Range::default(),
16074                replace: lsp::Range::default(),
16075            },
16076        )),
16077        ..lsp::CompletionItem::default()
16078    };
16079    let items = iter::once(item_0.clone())
16080        .chain((11..51).map(|i| lsp::CompletionItem {
16081            label: format!("item_{}", i),
16082            insert_text: Some(format!("item_{}", i)),
16083            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16084            ..lsp::CompletionItem::default()
16085        }))
16086        .collect::<Vec<_>>();
16087
16088    let default_commit_characters = vec!["?".to_string()];
16089    let default_data = json!({ "default": "data"});
16090    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16091    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16092    let default_edit_range = lsp::Range {
16093        start: lsp::Position {
16094            line: 0,
16095            character: 5,
16096        },
16097        end: lsp::Position {
16098            line: 0,
16099            character: 5,
16100        },
16101    };
16102
16103    let mut cx = EditorLspTestContext::new_rust(
16104        lsp::ServerCapabilities {
16105            completion_provider: Some(lsp::CompletionOptions {
16106                trigger_characters: Some(vec![".".to_string()]),
16107                resolve_provider: Some(true),
16108                ..Default::default()
16109            }),
16110            ..Default::default()
16111        },
16112        cx,
16113    )
16114    .await;
16115
16116    cx.set_state("fn main() { let a = 2ˇ; }");
16117    cx.simulate_keystroke(".");
16118
16119    let completion_data = default_data.clone();
16120    let completion_characters = default_commit_characters.clone();
16121    let completion_items = items.clone();
16122    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16123        let default_data = completion_data.clone();
16124        let default_commit_characters = completion_characters.clone();
16125        let items = completion_items.clone();
16126        async move {
16127            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16128                items,
16129                item_defaults: Some(lsp::CompletionListItemDefaults {
16130                    data: Some(default_data.clone()),
16131                    commit_characters: Some(default_commit_characters.clone()),
16132                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16133                        default_edit_range,
16134                    )),
16135                    insert_text_format: Some(default_insert_text_format),
16136                    insert_text_mode: Some(default_insert_text_mode),
16137                }),
16138                ..lsp::CompletionList::default()
16139            })))
16140        }
16141    })
16142    .next()
16143    .await;
16144
16145    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16146    cx.lsp
16147        .server
16148        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16149            let closure_resolved_items = resolved_items.clone();
16150            move |item_to_resolve, _| {
16151                let closure_resolved_items = closure_resolved_items.clone();
16152                async move {
16153                    closure_resolved_items.lock().push(item_to_resolve.clone());
16154                    Ok(item_to_resolve)
16155                }
16156            }
16157        })
16158        .detach();
16159
16160    cx.condition(|editor, _| editor.context_menu_visible())
16161        .await;
16162    cx.run_until_parked();
16163    cx.update_editor(|editor, _, _| {
16164        let menu = editor.context_menu.borrow_mut();
16165        match menu.as_ref().expect("should have the completions menu") {
16166            CodeContextMenu::Completions(completions_menu) => {
16167                assert_eq!(
16168                    completions_menu
16169                        .entries
16170                        .borrow()
16171                        .iter()
16172                        .map(|mat| mat.string.clone())
16173                        .collect::<Vec<String>>(),
16174                    items
16175                        .iter()
16176                        .map(|completion| completion.label.clone())
16177                        .collect::<Vec<String>>()
16178                );
16179            }
16180            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16181        }
16182    });
16183    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16184    // with 4 from the end.
16185    assert_eq!(
16186        *resolved_items.lock(),
16187        [&items[0..16], &items[items.len() - 4..items.len()]]
16188            .concat()
16189            .iter()
16190            .cloned()
16191            .map(|mut item| {
16192                if item.data.is_none() {
16193                    item.data = Some(default_data.clone());
16194                }
16195                item
16196            })
16197            .collect::<Vec<lsp::CompletionItem>>(),
16198        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16199    );
16200    resolved_items.lock().clear();
16201
16202    cx.update_editor(|editor, window, cx| {
16203        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16204    });
16205    cx.run_until_parked();
16206    // Completions that have already been resolved are skipped.
16207    assert_eq!(
16208        *resolved_items.lock(),
16209        items[items.len() - 17..items.len() - 4]
16210            .iter()
16211            .cloned()
16212            .map(|mut item| {
16213                if item.data.is_none() {
16214                    item.data = Some(default_data.clone());
16215                }
16216                item
16217            })
16218            .collect::<Vec<lsp::CompletionItem>>()
16219    );
16220    resolved_items.lock().clear();
16221}
16222
16223#[gpui::test]
16224async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16225    init_test(cx, |_| {});
16226
16227    let mut cx = EditorLspTestContext::new(
16228        Language::new(
16229            LanguageConfig {
16230                matcher: LanguageMatcher {
16231                    path_suffixes: vec!["jsx".into()],
16232                    ..Default::default()
16233                },
16234                overrides: [(
16235                    "element".into(),
16236                    LanguageConfigOverride {
16237                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16238                        ..Default::default()
16239                    },
16240                )]
16241                .into_iter()
16242                .collect(),
16243                ..Default::default()
16244            },
16245            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16246        )
16247        .with_override_query("(jsx_self_closing_element) @element")
16248        .unwrap(),
16249        lsp::ServerCapabilities {
16250            completion_provider: Some(lsp::CompletionOptions {
16251                trigger_characters: Some(vec![":".to_string()]),
16252                ..Default::default()
16253            }),
16254            ..Default::default()
16255        },
16256        cx,
16257    )
16258    .await;
16259
16260    cx.lsp
16261        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16262            Ok(Some(lsp::CompletionResponse::Array(vec![
16263                lsp::CompletionItem {
16264                    label: "bg-blue".into(),
16265                    ..Default::default()
16266                },
16267                lsp::CompletionItem {
16268                    label: "bg-red".into(),
16269                    ..Default::default()
16270                },
16271                lsp::CompletionItem {
16272                    label: "bg-yellow".into(),
16273                    ..Default::default()
16274                },
16275            ])))
16276        });
16277
16278    cx.set_state(r#"<p class="bgˇ" />"#);
16279
16280    // Trigger completion when typing a dash, because the dash is an extra
16281    // word character in the 'element' scope, which contains the cursor.
16282    cx.simulate_keystroke("-");
16283    cx.executor().run_until_parked();
16284    cx.update_editor(|editor, _, _| {
16285        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16286        {
16287            assert_eq!(
16288                completion_menu_entries(&menu),
16289                &["bg-blue", "bg-red", "bg-yellow"]
16290            );
16291        } else {
16292            panic!("expected completion menu to be open");
16293        }
16294    });
16295
16296    cx.simulate_keystroke("l");
16297    cx.executor().run_until_parked();
16298    cx.update_editor(|editor, _, _| {
16299        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16300        {
16301            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16302        } else {
16303            panic!("expected completion menu to be open");
16304        }
16305    });
16306
16307    // When filtering completions, consider the character after the '-' to
16308    // be the start of a subword.
16309    cx.set_state(r#"<p class="yelˇ" />"#);
16310    cx.simulate_keystroke("l");
16311    cx.executor().run_until_parked();
16312    cx.update_editor(|editor, _, _| {
16313        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16314        {
16315            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16316        } else {
16317            panic!("expected completion menu to be open");
16318        }
16319    });
16320}
16321
16322fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16323    let entries = menu.entries.borrow();
16324    entries.iter().map(|mat| mat.string.clone()).collect()
16325}
16326
16327#[gpui::test]
16328async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16329    init_test(cx, |settings| {
16330        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16331            Formatter::Prettier,
16332        )))
16333    });
16334
16335    let fs = FakeFs::new(cx.executor());
16336    fs.insert_file(path!("/file.ts"), Default::default()).await;
16337
16338    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16339    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16340
16341    language_registry.add(Arc::new(Language::new(
16342        LanguageConfig {
16343            name: "TypeScript".into(),
16344            matcher: LanguageMatcher {
16345                path_suffixes: vec!["ts".to_string()],
16346                ..Default::default()
16347            },
16348            ..Default::default()
16349        },
16350        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16351    )));
16352    update_test_language_settings(cx, |settings| {
16353        settings.defaults.prettier = Some(PrettierSettings {
16354            allowed: true,
16355            ..PrettierSettings::default()
16356        });
16357    });
16358
16359    let test_plugin = "test_plugin";
16360    let _ = language_registry.register_fake_lsp(
16361        "TypeScript",
16362        FakeLspAdapter {
16363            prettier_plugins: vec![test_plugin],
16364            ..Default::default()
16365        },
16366    );
16367
16368    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16369    let buffer = project
16370        .update(cx, |project, cx| {
16371            project.open_local_buffer(path!("/file.ts"), cx)
16372        })
16373        .await
16374        .unwrap();
16375
16376    let buffer_text = "one\ntwo\nthree\n";
16377    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16378    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16379    editor.update_in(cx, |editor, window, cx| {
16380        editor.set_text(buffer_text, window, cx)
16381    });
16382
16383    editor
16384        .update_in(cx, |editor, window, cx| {
16385            editor.perform_format(
16386                project.clone(),
16387                FormatTrigger::Manual,
16388                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16389                window,
16390                cx,
16391            )
16392        })
16393        .unwrap()
16394        .await;
16395    assert_eq!(
16396        editor.update(cx, |editor, cx| editor.text(cx)),
16397        buffer_text.to_string() + prettier_format_suffix,
16398        "Test prettier formatting was not applied to the original buffer text",
16399    );
16400
16401    update_test_language_settings(cx, |settings| {
16402        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16403    });
16404    let format = editor.update_in(cx, |editor, window, cx| {
16405        editor.perform_format(
16406            project.clone(),
16407            FormatTrigger::Manual,
16408            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16409            window,
16410            cx,
16411        )
16412    });
16413    format.await.unwrap();
16414    assert_eq!(
16415        editor.update(cx, |editor, cx| editor.text(cx)),
16416        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16417        "Autoformatting (via test prettier) was not applied to the original buffer text",
16418    );
16419}
16420
16421#[gpui::test]
16422async fn test_addition_reverts(cx: &mut TestAppContext) {
16423    init_test(cx, |_| {});
16424    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16425    let base_text = indoc! {r#"
16426        struct Row;
16427        struct Row1;
16428        struct Row2;
16429
16430        struct Row4;
16431        struct Row5;
16432        struct Row6;
16433
16434        struct Row8;
16435        struct Row9;
16436        struct Row10;"#};
16437
16438    // When addition hunks are not adjacent to carets, no hunk revert is performed
16439    assert_hunk_revert(
16440        indoc! {r#"struct Row;
16441                   struct Row1;
16442                   struct Row1.1;
16443                   struct Row1.2;
16444                   struct Row2;ˇ
16445
16446                   struct Row4;
16447                   struct Row5;
16448                   struct Row6;
16449
16450                   struct Row8;
16451                   ˇstruct Row9;
16452                   struct Row9.1;
16453                   struct Row9.2;
16454                   struct Row9.3;
16455                   struct Row10;"#},
16456        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16457        indoc! {r#"struct Row;
16458                   struct Row1;
16459                   struct Row1.1;
16460                   struct Row1.2;
16461                   struct Row2;ˇ
16462
16463                   struct Row4;
16464                   struct Row5;
16465                   struct Row6;
16466
16467                   struct Row8;
16468                   ˇstruct Row9;
16469                   struct Row9.1;
16470                   struct Row9.2;
16471                   struct Row9.3;
16472                   struct Row10;"#},
16473        base_text,
16474        &mut cx,
16475    );
16476    // Same for selections
16477    assert_hunk_revert(
16478        indoc! {r#"struct Row;
16479                   struct Row1;
16480                   struct Row2;
16481                   struct Row2.1;
16482                   struct Row2.2;
16483                   «ˇ
16484                   struct Row4;
16485                   struct» Row5;
16486                   «struct Row6;
16487                   ˇ»
16488                   struct Row9.1;
16489                   struct Row9.2;
16490                   struct Row9.3;
16491                   struct Row8;
16492                   struct Row9;
16493                   struct Row10;"#},
16494        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16495        indoc! {r#"struct Row;
16496                   struct Row1;
16497                   struct Row2;
16498                   struct Row2.1;
16499                   struct Row2.2;
16500                   «ˇ
16501                   struct Row4;
16502                   struct» Row5;
16503                   «struct Row6;
16504                   ˇ»
16505                   struct Row9.1;
16506                   struct Row9.2;
16507                   struct Row9.3;
16508                   struct Row8;
16509                   struct Row9;
16510                   struct Row10;"#},
16511        base_text,
16512        &mut cx,
16513    );
16514
16515    // When carets and selections intersect the addition hunks, those are reverted.
16516    // Adjacent carets got merged.
16517    assert_hunk_revert(
16518        indoc! {r#"struct Row;
16519                   ˇ// something on the top
16520                   struct Row1;
16521                   struct Row2;
16522                   struct Roˇw3.1;
16523                   struct Row2.2;
16524                   struct Row2.3;ˇ
16525
16526                   struct Row4;
16527                   struct ˇRow5.1;
16528                   struct Row5.2;
16529                   struct «Rowˇ»5.3;
16530                   struct Row5;
16531                   struct Row6;
16532                   ˇ
16533                   struct Row9.1;
16534                   struct «Rowˇ»9.2;
16535                   struct «ˇRow»9.3;
16536                   struct Row8;
16537                   struct Row9;
16538                   «ˇ// something on bottom»
16539                   struct Row10;"#},
16540        vec![
16541            DiffHunkStatusKind::Added,
16542            DiffHunkStatusKind::Added,
16543            DiffHunkStatusKind::Added,
16544            DiffHunkStatusKind::Added,
16545            DiffHunkStatusKind::Added,
16546        ],
16547        indoc! {r#"struct Row;
16548                   ˇstruct Row1;
16549                   struct Row2;
16550                   ˇ
16551                   struct Row4;
16552                   ˇstruct Row5;
16553                   struct Row6;
16554                   ˇ
16555                   ˇstruct Row8;
16556                   struct Row9;
16557                   ˇstruct Row10;"#},
16558        base_text,
16559        &mut cx,
16560    );
16561}
16562
16563#[gpui::test]
16564async fn test_modification_reverts(cx: &mut TestAppContext) {
16565    init_test(cx, |_| {});
16566    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16567    let base_text = indoc! {r#"
16568        struct Row;
16569        struct Row1;
16570        struct Row2;
16571
16572        struct Row4;
16573        struct Row5;
16574        struct Row6;
16575
16576        struct Row8;
16577        struct Row9;
16578        struct Row10;"#};
16579
16580    // Modification hunks behave the same as the addition ones.
16581    assert_hunk_revert(
16582        indoc! {r#"struct Row;
16583                   struct Row1;
16584                   struct Row33;
16585                   ˇ
16586                   struct Row4;
16587                   struct Row5;
16588                   struct Row6;
16589                   ˇ
16590                   struct Row99;
16591                   struct Row9;
16592                   struct Row10;"#},
16593        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16594        indoc! {r#"struct Row;
16595                   struct Row1;
16596                   struct Row33;
16597                   ˇ
16598                   struct Row4;
16599                   struct Row5;
16600                   struct Row6;
16601                   ˇ
16602                   struct Row99;
16603                   struct Row9;
16604                   struct Row10;"#},
16605        base_text,
16606        &mut cx,
16607    );
16608    assert_hunk_revert(
16609        indoc! {r#"struct Row;
16610                   struct Row1;
16611                   struct Row33;
16612                   «ˇ
16613                   struct Row4;
16614                   struct» Row5;
16615                   «struct Row6;
16616                   ˇ»
16617                   struct Row99;
16618                   struct Row9;
16619                   struct Row10;"#},
16620        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16621        indoc! {r#"struct Row;
16622                   struct Row1;
16623                   struct Row33;
16624                   «ˇ
16625                   struct Row4;
16626                   struct» Row5;
16627                   «struct Row6;
16628                   ˇ»
16629                   struct Row99;
16630                   struct Row9;
16631                   struct Row10;"#},
16632        base_text,
16633        &mut cx,
16634    );
16635
16636    assert_hunk_revert(
16637        indoc! {r#"ˇstruct Row1.1;
16638                   struct Row1;
16639                   «ˇstr»uct Row22;
16640
16641                   struct ˇRow44;
16642                   struct Row5;
16643                   struct «Rˇ»ow66;ˇ
16644
16645                   «struˇ»ct Row88;
16646                   struct Row9;
16647                   struct Row1011;ˇ"#},
16648        vec![
16649            DiffHunkStatusKind::Modified,
16650            DiffHunkStatusKind::Modified,
16651            DiffHunkStatusKind::Modified,
16652            DiffHunkStatusKind::Modified,
16653            DiffHunkStatusKind::Modified,
16654            DiffHunkStatusKind::Modified,
16655        ],
16656        indoc! {r#"struct Row;
16657                   ˇstruct Row1;
16658                   struct Row2;
16659                   ˇ
16660                   struct Row4;
16661                   ˇstruct Row5;
16662                   struct Row6;
16663                   ˇ
16664                   struct Row8;
16665                   ˇstruct Row9;
16666                   struct Row10;ˇ"#},
16667        base_text,
16668        &mut cx,
16669    );
16670}
16671
16672#[gpui::test]
16673async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16674    init_test(cx, |_| {});
16675    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16676    let base_text = indoc! {r#"
16677        one
16678
16679        two
16680        three
16681        "#};
16682
16683    cx.set_head_text(base_text);
16684    cx.set_state("\nˇ\n");
16685    cx.executor().run_until_parked();
16686    cx.update_editor(|editor, _window, cx| {
16687        editor.expand_selected_diff_hunks(cx);
16688    });
16689    cx.executor().run_until_parked();
16690    cx.update_editor(|editor, window, cx| {
16691        editor.backspace(&Default::default(), window, cx);
16692    });
16693    cx.run_until_parked();
16694    cx.assert_state_with_diff(
16695        indoc! {r#"
16696
16697        - two
16698        - threeˇ
16699        +
16700        "#}
16701        .to_string(),
16702    );
16703}
16704
16705#[gpui::test]
16706async fn test_deletion_reverts(cx: &mut TestAppContext) {
16707    init_test(cx, |_| {});
16708    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16709    let base_text = indoc! {r#"struct Row;
16710struct Row1;
16711struct Row2;
16712
16713struct Row4;
16714struct Row5;
16715struct Row6;
16716
16717struct Row8;
16718struct Row9;
16719struct Row10;"#};
16720
16721    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16722    assert_hunk_revert(
16723        indoc! {r#"struct Row;
16724                   struct Row2;
16725
16726                   ˇstruct Row4;
16727                   struct Row5;
16728                   struct Row6;
16729                   ˇ
16730                   struct Row8;
16731                   struct Row10;"#},
16732        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16733        indoc! {r#"struct Row;
16734                   struct Row2;
16735
16736                   ˇstruct Row4;
16737                   struct Row5;
16738                   struct Row6;
16739                   ˇ
16740                   struct Row8;
16741                   struct Row10;"#},
16742        base_text,
16743        &mut cx,
16744    );
16745    assert_hunk_revert(
16746        indoc! {r#"struct Row;
16747                   struct Row2;
16748
16749                   «ˇstruct Row4;
16750                   struct» Row5;
16751                   «struct Row6;
16752                   ˇ»
16753                   struct Row8;
16754                   struct Row10;"#},
16755        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16756        indoc! {r#"struct Row;
16757                   struct Row2;
16758
16759                   «ˇstruct Row4;
16760                   struct» Row5;
16761                   «struct Row6;
16762                   ˇ»
16763                   struct Row8;
16764                   struct Row10;"#},
16765        base_text,
16766        &mut cx,
16767    );
16768
16769    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16770    assert_hunk_revert(
16771        indoc! {r#"struct Row;
16772                   ˇstruct Row2;
16773
16774                   struct Row4;
16775                   struct Row5;
16776                   struct Row6;
16777
16778                   struct Row8;ˇ
16779                   struct Row10;"#},
16780        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16781        indoc! {r#"struct Row;
16782                   struct Row1;
16783                   ˇstruct Row2;
16784
16785                   struct Row4;
16786                   struct Row5;
16787                   struct Row6;
16788
16789                   struct Row8;ˇ
16790                   struct Row9;
16791                   struct Row10;"#},
16792        base_text,
16793        &mut cx,
16794    );
16795    assert_hunk_revert(
16796        indoc! {r#"struct Row;
16797                   struct Row2«ˇ;
16798                   struct Row4;
16799                   struct» Row5;
16800                   «struct Row6;
16801
16802                   struct Row8;ˇ»
16803                   struct Row10;"#},
16804        vec![
16805            DiffHunkStatusKind::Deleted,
16806            DiffHunkStatusKind::Deleted,
16807            DiffHunkStatusKind::Deleted,
16808        ],
16809        indoc! {r#"struct Row;
16810                   struct Row1;
16811                   struct Row2«ˇ;
16812
16813                   struct Row4;
16814                   struct» Row5;
16815                   «struct Row6;
16816
16817                   struct Row8;ˇ»
16818                   struct Row9;
16819                   struct Row10;"#},
16820        base_text,
16821        &mut cx,
16822    );
16823}
16824
16825#[gpui::test]
16826async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16827    init_test(cx, |_| {});
16828
16829    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16830    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16831    let base_text_3 =
16832        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16833
16834    let text_1 = edit_first_char_of_every_line(base_text_1);
16835    let text_2 = edit_first_char_of_every_line(base_text_2);
16836    let text_3 = edit_first_char_of_every_line(base_text_3);
16837
16838    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16839    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16840    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16841
16842    let multibuffer = cx.new(|cx| {
16843        let mut multibuffer = MultiBuffer::new(ReadWrite);
16844        multibuffer.push_excerpts(
16845            buffer_1.clone(),
16846            [
16847                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16848                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16849                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16850            ],
16851            cx,
16852        );
16853        multibuffer.push_excerpts(
16854            buffer_2.clone(),
16855            [
16856                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16857                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16858                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16859            ],
16860            cx,
16861        );
16862        multibuffer.push_excerpts(
16863            buffer_3.clone(),
16864            [
16865                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16866                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16867                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16868            ],
16869            cx,
16870        );
16871        multibuffer
16872    });
16873
16874    let fs = FakeFs::new(cx.executor());
16875    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16876    let (editor, cx) = cx
16877        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16878    editor.update_in(cx, |editor, _window, cx| {
16879        for (buffer, diff_base) in [
16880            (buffer_1.clone(), base_text_1),
16881            (buffer_2.clone(), base_text_2),
16882            (buffer_3.clone(), base_text_3),
16883        ] {
16884            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16885            editor
16886                .buffer
16887                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16888        }
16889    });
16890    cx.executor().run_until_parked();
16891
16892    editor.update_in(cx, |editor, window, cx| {
16893        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}");
16894        editor.select_all(&SelectAll, window, cx);
16895        editor.git_restore(&Default::default(), window, cx);
16896    });
16897    cx.executor().run_until_parked();
16898
16899    // When all ranges are selected, all buffer hunks are reverted.
16900    editor.update(cx, |editor, cx| {
16901        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");
16902    });
16903    buffer_1.update(cx, |buffer, _| {
16904        assert_eq!(buffer.text(), base_text_1);
16905    });
16906    buffer_2.update(cx, |buffer, _| {
16907        assert_eq!(buffer.text(), base_text_2);
16908    });
16909    buffer_3.update(cx, |buffer, _| {
16910        assert_eq!(buffer.text(), base_text_3);
16911    });
16912
16913    editor.update_in(cx, |editor, window, cx| {
16914        editor.undo(&Default::default(), window, cx);
16915    });
16916
16917    editor.update_in(cx, |editor, window, cx| {
16918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16919            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16920        });
16921        editor.git_restore(&Default::default(), window, cx);
16922    });
16923
16924    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16925    // but not affect buffer_2 and its related excerpts.
16926    editor.update(cx, |editor, cx| {
16927        assert_eq!(
16928            editor.text(cx),
16929            "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}"
16930        );
16931    });
16932    buffer_1.update(cx, |buffer, _| {
16933        assert_eq!(buffer.text(), base_text_1);
16934    });
16935    buffer_2.update(cx, |buffer, _| {
16936        assert_eq!(
16937            buffer.text(),
16938            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16939        );
16940    });
16941    buffer_3.update(cx, |buffer, _| {
16942        assert_eq!(
16943            buffer.text(),
16944            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16945        );
16946    });
16947
16948    fn edit_first_char_of_every_line(text: &str) -> String {
16949        text.split('\n')
16950            .map(|line| format!("X{}", &line[1..]))
16951            .collect::<Vec<_>>()
16952            .join("\n")
16953    }
16954}
16955
16956#[gpui::test]
16957async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16958    init_test(cx, |_| {});
16959
16960    let cols = 4;
16961    let rows = 10;
16962    let sample_text_1 = sample_text(rows, cols, 'a');
16963    assert_eq!(
16964        sample_text_1,
16965        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16966    );
16967    let sample_text_2 = sample_text(rows, cols, 'l');
16968    assert_eq!(
16969        sample_text_2,
16970        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16971    );
16972    let sample_text_3 = sample_text(rows, cols, 'v');
16973    assert_eq!(
16974        sample_text_3,
16975        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16976    );
16977
16978    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16979    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16980    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16981
16982    let multi_buffer = cx.new(|cx| {
16983        let mut multibuffer = MultiBuffer::new(ReadWrite);
16984        multibuffer.push_excerpts(
16985            buffer_1.clone(),
16986            [
16987                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16988                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16989                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16990            ],
16991            cx,
16992        );
16993        multibuffer.push_excerpts(
16994            buffer_2.clone(),
16995            [
16996                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16997                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16998                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16999            ],
17000            cx,
17001        );
17002        multibuffer.push_excerpts(
17003            buffer_3.clone(),
17004            [
17005                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17006                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17007                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17008            ],
17009            cx,
17010        );
17011        multibuffer
17012    });
17013
17014    let fs = FakeFs::new(cx.executor());
17015    fs.insert_tree(
17016        "/a",
17017        json!({
17018            "main.rs": sample_text_1,
17019            "other.rs": sample_text_2,
17020            "lib.rs": sample_text_3,
17021        }),
17022    )
17023    .await;
17024    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17025    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17026    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17027    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17028        Editor::new(
17029            EditorMode::full(),
17030            multi_buffer,
17031            Some(project.clone()),
17032            window,
17033            cx,
17034        )
17035    });
17036    let multibuffer_item_id = workspace
17037        .update(cx, |workspace, window, cx| {
17038            assert!(
17039                workspace.active_item(cx).is_none(),
17040                "active item should be None before the first item is added"
17041            );
17042            workspace.add_item_to_active_pane(
17043                Box::new(multi_buffer_editor.clone()),
17044                None,
17045                true,
17046                window,
17047                cx,
17048            );
17049            let active_item = workspace
17050                .active_item(cx)
17051                .expect("should have an active item after adding the multi buffer");
17052            assert!(
17053                !active_item.is_singleton(cx),
17054                "A multi buffer was expected to active after adding"
17055            );
17056            active_item.item_id()
17057        })
17058        .unwrap();
17059    cx.executor().run_until_parked();
17060
17061    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17062        editor.change_selections(
17063            SelectionEffects::scroll(Autoscroll::Next),
17064            window,
17065            cx,
17066            |s| s.select_ranges(Some(1..2)),
17067        );
17068        editor.open_excerpts(&OpenExcerpts, window, cx);
17069    });
17070    cx.executor().run_until_parked();
17071    let first_item_id = workspace
17072        .update(cx, |workspace, window, cx| {
17073            let active_item = workspace
17074                .active_item(cx)
17075                .expect("should have an active item after navigating into the 1st buffer");
17076            let first_item_id = active_item.item_id();
17077            assert_ne!(
17078                first_item_id, multibuffer_item_id,
17079                "Should navigate into the 1st buffer and activate it"
17080            );
17081            assert!(
17082                active_item.is_singleton(cx),
17083                "New active item should be a singleton buffer"
17084            );
17085            assert_eq!(
17086                active_item
17087                    .act_as::<Editor>(cx)
17088                    .expect("should have navigated into an editor for the 1st buffer")
17089                    .read(cx)
17090                    .text(cx),
17091                sample_text_1
17092            );
17093
17094            workspace
17095                .go_back(workspace.active_pane().downgrade(), window, cx)
17096                .detach_and_log_err(cx);
17097
17098            first_item_id
17099        })
17100        .unwrap();
17101    cx.executor().run_until_parked();
17102    workspace
17103        .update(cx, |workspace, _, cx| {
17104            let active_item = workspace
17105                .active_item(cx)
17106                .expect("should have an active item after navigating back");
17107            assert_eq!(
17108                active_item.item_id(),
17109                multibuffer_item_id,
17110                "Should navigate back to the multi buffer"
17111            );
17112            assert!(!active_item.is_singleton(cx));
17113        })
17114        .unwrap();
17115
17116    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17117        editor.change_selections(
17118            SelectionEffects::scroll(Autoscroll::Next),
17119            window,
17120            cx,
17121            |s| s.select_ranges(Some(39..40)),
17122        );
17123        editor.open_excerpts(&OpenExcerpts, window, cx);
17124    });
17125    cx.executor().run_until_parked();
17126    let second_item_id = workspace
17127        .update(cx, |workspace, window, cx| {
17128            let active_item = workspace
17129                .active_item(cx)
17130                .expect("should have an active item after navigating into the 2nd buffer");
17131            let second_item_id = active_item.item_id();
17132            assert_ne!(
17133                second_item_id, multibuffer_item_id,
17134                "Should navigate away from the multibuffer"
17135            );
17136            assert_ne!(
17137                second_item_id, first_item_id,
17138                "Should navigate into the 2nd buffer and activate it"
17139            );
17140            assert!(
17141                active_item.is_singleton(cx),
17142                "New active item should be a singleton buffer"
17143            );
17144            assert_eq!(
17145                active_item
17146                    .act_as::<Editor>(cx)
17147                    .expect("should have navigated into an editor")
17148                    .read(cx)
17149                    .text(cx),
17150                sample_text_2
17151            );
17152
17153            workspace
17154                .go_back(workspace.active_pane().downgrade(), window, cx)
17155                .detach_and_log_err(cx);
17156
17157            second_item_id
17158        })
17159        .unwrap();
17160    cx.executor().run_until_parked();
17161    workspace
17162        .update(cx, |workspace, _, cx| {
17163            let active_item = workspace
17164                .active_item(cx)
17165                .expect("should have an active item after navigating back from the 2nd buffer");
17166            assert_eq!(
17167                active_item.item_id(),
17168                multibuffer_item_id,
17169                "Should navigate back from the 2nd buffer to the multi buffer"
17170            );
17171            assert!(!active_item.is_singleton(cx));
17172        })
17173        .unwrap();
17174
17175    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17176        editor.change_selections(
17177            SelectionEffects::scroll(Autoscroll::Next),
17178            window,
17179            cx,
17180            |s| s.select_ranges(Some(70..70)),
17181        );
17182        editor.open_excerpts(&OpenExcerpts, window, cx);
17183    });
17184    cx.executor().run_until_parked();
17185    workspace
17186        .update(cx, |workspace, window, cx| {
17187            let active_item = workspace
17188                .active_item(cx)
17189                .expect("should have an active item after navigating into the 3rd buffer");
17190            let third_item_id = active_item.item_id();
17191            assert_ne!(
17192                third_item_id, multibuffer_item_id,
17193                "Should navigate into the 3rd buffer and activate it"
17194            );
17195            assert_ne!(third_item_id, first_item_id);
17196            assert_ne!(third_item_id, second_item_id);
17197            assert!(
17198                active_item.is_singleton(cx),
17199                "New active item should be a singleton buffer"
17200            );
17201            assert_eq!(
17202                active_item
17203                    .act_as::<Editor>(cx)
17204                    .expect("should have navigated into an editor")
17205                    .read(cx)
17206                    .text(cx),
17207                sample_text_3
17208            );
17209
17210            workspace
17211                .go_back(workspace.active_pane().downgrade(), window, cx)
17212                .detach_and_log_err(cx);
17213        })
17214        .unwrap();
17215    cx.executor().run_until_parked();
17216    workspace
17217        .update(cx, |workspace, _, cx| {
17218            let active_item = workspace
17219                .active_item(cx)
17220                .expect("should have an active item after navigating back from the 3rd buffer");
17221            assert_eq!(
17222                active_item.item_id(),
17223                multibuffer_item_id,
17224                "Should navigate back from the 3rd buffer to the multi buffer"
17225            );
17226            assert!(!active_item.is_singleton(cx));
17227        })
17228        .unwrap();
17229}
17230
17231#[gpui::test]
17232async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17233    init_test(cx, |_| {});
17234
17235    let mut cx = EditorTestContext::new(cx).await;
17236
17237    let diff_base = r#"
17238        use some::mod;
17239
17240        const A: u32 = 42;
17241
17242        fn main() {
17243            println!("hello");
17244
17245            println!("world");
17246        }
17247        "#
17248    .unindent();
17249
17250    cx.set_state(
17251        &r#"
17252        use some::modified;
17253
17254        ˇ
17255        fn main() {
17256            println!("hello there");
17257
17258            println!("around the");
17259            println!("world");
17260        }
17261        "#
17262        .unindent(),
17263    );
17264
17265    cx.set_head_text(&diff_base);
17266    executor.run_until_parked();
17267
17268    cx.update_editor(|editor, window, cx| {
17269        editor.go_to_next_hunk(&GoToHunk, window, cx);
17270        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17271    });
17272    executor.run_until_parked();
17273    cx.assert_state_with_diff(
17274        r#"
17275          use some::modified;
17276
17277
17278          fn main() {
17279        -     println!("hello");
17280        + ˇ    println!("hello there");
17281
17282              println!("around the");
17283              println!("world");
17284          }
17285        "#
17286        .unindent(),
17287    );
17288
17289    cx.update_editor(|editor, window, cx| {
17290        for _ in 0..2 {
17291            editor.go_to_next_hunk(&GoToHunk, window, cx);
17292            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17293        }
17294    });
17295    executor.run_until_parked();
17296    cx.assert_state_with_diff(
17297        r#"
17298        - use some::mod;
17299        + ˇuse some::modified;
17300
17301
17302          fn main() {
17303        -     println!("hello");
17304        +     println!("hello there");
17305
17306        +     println!("around the");
17307              println!("world");
17308          }
17309        "#
17310        .unindent(),
17311    );
17312
17313    cx.update_editor(|editor, window, cx| {
17314        editor.go_to_next_hunk(&GoToHunk, window, cx);
17315        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17316    });
17317    executor.run_until_parked();
17318    cx.assert_state_with_diff(
17319        r#"
17320        - use some::mod;
17321        + use some::modified;
17322
17323        - const A: u32 = 42;
17324          ˇ
17325          fn main() {
17326        -     println!("hello");
17327        +     println!("hello there");
17328
17329        +     println!("around the");
17330              println!("world");
17331          }
17332        "#
17333        .unindent(),
17334    );
17335
17336    cx.update_editor(|editor, window, cx| {
17337        editor.cancel(&Cancel, window, cx);
17338    });
17339
17340    cx.assert_state_with_diff(
17341        r#"
17342          use some::modified;
17343
17344          ˇ
17345          fn main() {
17346              println!("hello there");
17347
17348              println!("around the");
17349              println!("world");
17350          }
17351        "#
17352        .unindent(),
17353    );
17354}
17355
17356#[gpui::test]
17357async fn test_diff_base_change_with_expanded_diff_hunks(
17358    executor: BackgroundExecutor,
17359    cx: &mut TestAppContext,
17360) {
17361    init_test(cx, |_| {});
17362
17363    let mut cx = EditorTestContext::new(cx).await;
17364
17365    let diff_base = r#"
17366        use some::mod1;
17367        use some::mod2;
17368
17369        const A: u32 = 42;
17370        const B: u32 = 42;
17371        const C: u32 = 42;
17372
17373        fn main() {
17374            println!("hello");
17375
17376            println!("world");
17377        }
17378        "#
17379    .unindent();
17380
17381    cx.set_state(
17382        &r#"
17383        use some::mod2;
17384
17385        const A: u32 = 42;
17386        const C: u32 = 42;
17387
17388        fn main(ˇ) {
17389            //println!("hello");
17390
17391            println!("world");
17392            //
17393            //
17394        }
17395        "#
17396        .unindent(),
17397    );
17398
17399    cx.set_head_text(&diff_base);
17400    executor.run_until_parked();
17401
17402    cx.update_editor(|editor, window, cx| {
17403        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17404    });
17405    executor.run_until_parked();
17406    cx.assert_state_with_diff(
17407        r#"
17408        - use some::mod1;
17409          use some::mod2;
17410
17411          const A: u32 = 42;
17412        - const B: u32 = 42;
17413          const C: u32 = 42;
17414
17415          fn main(ˇ) {
17416        -     println!("hello");
17417        +     //println!("hello");
17418
17419              println!("world");
17420        +     //
17421        +     //
17422          }
17423        "#
17424        .unindent(),
17425    );
17426
17427    cx.set_head_text("new diff base!");
17428    executor.run_until_parked();
17429    cx.assert_state_with_diff(
17430        r#"
17431        - new diff base!
17432        + use some::mod2;
17433        +
17434        + const A: u32 = 42;
17435        + const C: u32 = 42;
17436        +
17437        + fn main(ˇ) {
17438        +     //println!("hello");
17439        +
17440        +     println!("world");
17441        +     //
17442        +     //
17443        + }
17444        "#
17445        .unindent(),
17446    );
17447}
17448
17449#[gpui::test]
17450async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17451    init_test(cx, |_| {});
17452
17453    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17454    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17455    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17456    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17457    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17458    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17459
17460    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17461    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17462    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17463
17464    let multi_buffer = cx.new(|cx| {
17465        let mut multibuffer = MultiBuffer::new(ReadWrite);
17466        multibuffer.push_excerpts(
17467            buffer_1.clone(),
17468            [
17469                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17470                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17471                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17472            ],
17473            cx,
17474        );
17475        multibuffer.push_excerpts(
17476            buffer_2.clone(),
17477            [
17478                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17479                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17480                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17481            ],
17482            cx,
17483        );
17484        multibuffer.push_excerpts(
17485            buffer_3.clone(),
17486            [
17487                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17488                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17489                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17490            ],
17491            cx,
17492        );
17493        multibuffer
17494    });
17495
17496    let editor =
17497        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17498    editor
17499        .update(cx, |editor, _window, cx| {
17500            for (buffer, diff_base) in [
17501                (buffer_1.clone(), file_1_old),
17502                (buffer_2.clone(), file_2_old),
17503                (buffer_3.clone(), file_3_old),
17504            ] {
17505                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17506                editor
17507                    .buffer
17508                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17509            }
17510        })
17511        .unwrap();
17512
17513    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17514    cx.run_until_parked();
17515
17516    cx.assert_editor_state(
17517        &"
17518            ˇaaa
17519            ccc
17520            ddd
17521
17522            ggg
17523            hhh
17524
17525
17526            lll
17527            mmm
17528            NNN
17529
17530            qqq
17531            rrr
17532
17533            uuu
17534            111
17535            222
17536            333
17537
17538            666
17539            777
17540
17541            000
17542            !!!"
17543        .unindent(),
17544    );
17545
17546    cx.update_editor(|editor, window, cx| {
17547        editor.select_all(&SelectAll, window, cx);
17548        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17549    });
17550    cx.executor().run_until_parked();
17551
17552    cx.assert_state_with_diff(
17553        "
17554            «aaa
17555          - bbb
17556            ccc
17557            ddd
17558
17559            ggg
17560            hhh
17561
17562
17563            lll
17564            mmm
17565          - nnn
17566          + NNN
17567
17568            qqq
17569            rrr
17570
17571            uuu
17572            111
17573            222
17574            333
17575
17576          + 666
17577            777
17578
17579            000
17580            !!!ˇ»"
17581            .unindent(),
17582    );
17583}
17584
17585#[gpui::test]
17586async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17587    init_test(cx, |_| {});
17588
17589    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17590    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17591
17592    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17593    let multi_buffer = cx.new(|cx| {
17594        let mut multibuffer = MultiBuffer::new(ReadWrite);
17595        multibuffer.push_excerpts(
17596            buffer.clone(),
17597            [
17598                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17599                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17600                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17601            ],
17602            cx,
17603        );
17604        multibuffer
17605    });
17606
17607    let editor =
17608        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17609    editor
17610        .update(cx, |editor, _window, cx| {
17611            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17612            editor
17613                .buffer
17614                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17615        })
17616        .unwrap();
17617
17618    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17619    cx.run_until_parked();
17620
17621    cx.update_editor(|editor, window, cx| {
17622        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17623    });
17624    cx.executor().run_until_parked();
17625
17626    // When the start of a hunk coincides with the start of its excerpt,
17627    // the hunk is expanded. When the start of a a hunk is earlier than
17628    // the start of its excerpt, the hunk is not expanded.
17629    cx.assert_state_with_diff(
17630        "
17631            ˇaaa
17632          - bbb
17633          + BBB
17634
17635          - ddd
17636          - eee
17637          + DDD
17638          + EEE
17639            fff
17640
17641            iii
17642        "
17643        .unindent(),
17644    );
17645}
17646
17647#[gpui::test]
17648async fn test_edits_around_expanded_insertion_hunks(
17649    executor: BackgroundExecutor,
17650    cx: &mut TestAppContext,
17651) {
17652    init_test(cx, |_| {});
17653
17654    let mut cx = EditorTestContext::new(cx).await;
17655
17656    let diff_base = r#"
17657        use some::mod1;
17658        use some::mod2;
17659
17660        const A: u32 = 42;
17661
17662        fn main() {
17663            println!("hello");
17664
17665            println!("world");
17666        }
17667        "#
17668    .unindent();
17669    executor.run_until_parked();
17670    cx.set_state(
17671        &r#"
17672        use some::mod1;
17673        use some::mod2;
17674
17675        const A: u32 = 42;
17676        const B: u32 = 42;
17677        const C: u32 = 42;
17678        ˇ
17679
17680        fn main() {
17681            println!("hello");
17682
17683            println!("world");
17684        }
17685        "#
17686        .unindent(),
17687    );
17688
17689    cx.set_head_text(&diff_base);
17690    executor.run_until_parked();
17691
17692    cx.update_editor(|editor, window, cx| {
17693        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17694    });
17695    executor.run_until_parked();
17696
17697    cx.assert_state_with_diff(
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 = 42;
17705      + ˇ
17706
17707        fn main() {
17708            println!("hello");
17709
17710            println!("world");
17711        }
17712      "#
17713        .unindent(),
17714    );
17715
17716    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17717    executor.run_until_parked();
17718
17719    cx.assert_state_with_diff(
17720        r#"
17721        use some::mod1;
17722        use some::mod2;
17723
17724        const A: u32 = 42;
17725      + const B: u32 = 42;
17726      + const C: u32 = 42;
17727      + const D: u32 = 42;
17728      + ˇ
17729
17730        fn main() {
17731            println!("hello");
17732
17733            println!("world");
17734        }
17735      "#
17736        .unindent(),
17737    );
17738
17739    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17740    executor.run_until_parked();
17741
17742    cx.assert_state_with_diff(
17743        r#"
17744        use some::mod1;
17745        use some::mod2;
17746
17747        const A: u32 = 42;
17748      + const B: u32 = 42;
17749      + const C: u32 = 42;
17750      + const D: u32 = 42;
17751      + const E: u32 = 42;
17752      + ˇ
17753
17754        fn main() {
17755            println!("hello");
17756
17757            println!("world");
17758        }
17759      "#
17760        .unindent(),
17761    );
17762
17763    cx.update_editor(|editor, window, cx| {
17764        editor.delete_line(&DeleteLine, window, cx);
17765    });
17766    executor.run_until_parked();
17767
17768    cx.assert_state_with_diff(
17769        r#"
17770        use some::mod1;
17771        use some::mod2;
17772
17773        const A: u32 = 42;
17774      + const B: u32 = 42;
17775      + const C: u32 = 42;
17776      + const D: u32 = 42;
17777      + const E: u32 = 42;
17778        ˇ
17779        fn main() {
17780            println!("hello");
17781
17782            println!("world");
17783        }
17784      "#
17785        .unindent(),
17786    );
17787
17788    cx.update_editor(|editor, window, cx| {
17789        editor.move_up(&MoveUp, window, cx);
17790        editor.delete_line(&DeleteLine, window, cx);
17791        editor.move_up(&MoveUp, window, cx);
17792        editor.delete_line(&DeleteLine, window, cx);
17793        editor.move_up(&MoveUp, window, cx);
17794        editor.delete_line(&DeleteLine, window, cx);
17795    });
17796    executor.run_until_parked();
17797    cx.assert_state_with_diff(
17798        r#"
17799        use some::mod1;
17800        use some::mod2;
17801
17802        const A: u32 = 42;
17803      + const B: u32 = 42;
17804        ˇ
17805        fn main() {
17806            println!("hello");
17807
17808            println!("world");
17809        }
17810      "#
17811        .unindent(),
17812    );
17813
17814    cx.update_editor(|editor, window, cx| {
17815        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17816        editor.delete_line(&DeleteLine, window, cx);
17817    });
17818    executor.run_until_parked();
17819    cx.assert_state_with_diff(
17820        r#"
17821        ˇ
17822        fn main() {
17823            println!("hello");
17824
17825            println!("world");
17826        }
17827      "#
17828        .unindent(),
17829    );
17830}
17831
17832#[gpui::test]
17833async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17834    init_test(cx, |_| {});
17835
17836    let mut cx = EditorTestContext::new(cx).await;
17837    cx.set_head_text(indoc! { "
17838        one
17839        two
17840        three
17841        four
17842        five
17843        "
17844    });
17845    cx.set_state(indoc! { "
17846        one
17847        ˇthree
17848        five
17849    "});
17850    cx.run_until_parked();
17851    cx.update_editor(|editor, window, cx| {
17852        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17853    });
17854    cx.assert_state_with_diff(
17855        indoc! { "
17856        one
17857      - two
17858        ˇthree
17859      - four
17860        five
17861    "}
17862        .to_string(),
17863    );
17864    cx.update_editor(|editor, window, cx| {
17865        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17866    });
17867
17868    cx.assert_state_with_diff(
17869        indoc! { "
17870        one
17871        ˇthree
17872        five
17873    "}
17874        .to_string(),
17875    );
17876
17877    cx.set_state(indoc! { "
17878        one
17879        ˇTWO
17880        three
17881        four
17882        five
17883    "});
17884    cx.run_until_parked();
17885    cx.update_editor(|editor, window, cx| {
17886        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17887    });
17888
17889    cx.assert_state_with_diff(
17890        indoc! { "
17891            one
17892          - two
17893          + ˇTWO
17894            three
17895            four
17896            five
17897        "}
17898        .to_string(),
17899    );
17900    cx.update_editor(|editor, window, cx| {
17901        editor.move_up(&Default::default(), window, cx);
17902        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17903    });
17904    cx.assert_state_with_diff(
17905        indoc! { "
17906            one
17907            ˇTWO
17908            three
17909            four
17910            five
17911        "}
17912        .to_string(),
17913    );
17914}
17915
17916#[gpui::test]
17917async fn test_edits_around_expanded_deletion_hunks(
17918    executor: BackgroundExecutor,
17919    cx: &mut TestAppContext,
17920) {
17921    init_test(cx, |_| {});
17922
17923    let mut cx = EditorTestContext::new(cx).await;
17924
17925    let diff_base = r#"
17926        use some::mod1;
17927        use some::mod2;
17928
17929        const A: u32 = 42;
17930        const B: u32 = 42;
17931        const C: u32 = 42;
17932
17933
17934        fn main() {
17935            println!("hello");
17936
17937            println!("world");
17938        }
17939    "#
17940    .unindent();
17941    executor.run_until_parked();
17942    cx.set_state(
17943        &r#"
17944        use some::mod1;
17945        use some::mod2;
17946
17947        ˇconst B: u32 = 42;
17948        const C: u32 = 42;
17949
17950
17951        fn main() {
17952            println!("hello");
17953
17954            println!("world");
17955        }
17956        "#
17957        .unindent(),
17958    );
17959
17960    cx.set_head_text(&diff_base);
17961    executor.run_until_parked();
17962
17963    cx.update_editor(|editor, window, cx| {
17964        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17965    });
17966    executor.run_until_parked();
17967
17968    cx.assert_state_with_diff(
17969        r#"
17970        use some::mod1;
17971        use some::mod2;
17972
17973      - const A: u32 = 42;
17974        ˇconst B: u32 = 42;
17975        const C: u32 = 42;
17976
17977
17978        fn main() {
17979            println!("hello");
17980
17981            println!("world");
17982        }
17983      "#
17984        .unindent(),
17985    );
17986
17987    cx.update_editor(|editor, window, cx| {
17988        editor.delete_line(&DeleteLine, window, cx);
17989    });
17990    executor.run_until_parked();
17991    cx.assert_state_with_diff(
17992        r#"
17993        use some::mod1;
17994        use some::mod2;
17995
17996      - const A: u32 = 42;
17997      - const B: u32 = 42;
17998        ˇconst C: u32 = 42;
17999
18000
18001        fn main() {
18002            println!("hello");
18003
18004            println!("world");
18005        }
18006      "#
18007        .unindent(),
18008    );
18009
18010    cx.update_editor(|editor, window, cx| {
18011        editor.delete_line(&DeleteLine, window, cx);
18012    });
18013    executor.run_until_parked();
18014    cx.assert_state_with_diff(
18015        r#"
18016        use some::mod1;
18017        use some::mod2;
18018
18019      - const A: u32 = 42;
18020      - const B: u32 = 42;
18021      - const C: u32 = 42;
18022        ˇ
18023
18024        fn main() {
18025            println!("hello");
18026
18027            println!("world");
18028        }
18029      "#
18030        .unindent(),
18031    );
18032
18033    cx.update_editor(|editor, window, cx| {
18034        editor.handle_input("replacement", window, cx);
18035    });
18036    executor.run_until_parked();
18037    cx.assert_state_with_diff(
18038        r#"
18039        use some::mod1;
18040        use some::mod2;
18041
18042      - const A: u32 = 42;
18043      - const B: u32 = 42;
18044      - const C: u32 = 42;
18045      -
18046      + replacementˇ
18047
18048        fn main() {
18049            println!("hello");
18050
18051            println!("world");
18052        }
18053      "#
18054        .unindent(),
18055    );
18056}
18057
18058#[gpui::test]
18059async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18060    init_test(cx, |_| {});
18061
18062    let mut cx = EditorTestContext::new(cx).await;
18063
18064    let base_text = r#"
18065        one
18066        two
18067        three
18068        four
18069        five
18070    "#
18071    .unindent();
18072    executor.run_until_parked();
18073    cx.set_state(
18074        &r#"
18075        one
18076        two
18077        fˇour
18078        five
18079        "#
18080        .unindent(),
18081    );
18082
18083    cx.set_head_text(&base_text);
18084    executor.run_until_parked();
18085
18086    cx.update_editor(|editor, window, cx| {
18087        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18088    });
18089    executor.run_until_parked();
18090
18091    cx.assert_state_with_diff(
18092        r#"
18093          one
18094          two
18095        - three
18096          fˇour
18097          five
18098        "#
18099        .unindent(),
18100    );
18101
18102    cx.update_editor(|editor, window, cx| {
18103        editor.backspace(&Backspace, window, cx);
18104        editor.backspace(&Backspace, window, cx);
18105    });
18106    executor.run_until_parked();
18107    cx.assert_state_with_diff(
18108        r#"
18109          one
18110          two
18111        - threeˇ
18112        - four
18113        + our
18114          five
18115        "#
18116        .unindent(),
18117    );
18118}
18119
18120#[gpui::test]
18121async fn test_edit_after_expanded_modification_hunk(
18122    executor: BackgroundExecutor,
18123    cx: &mut TestAppContext,
18124) {
18125    init_test(cx, |_| {});
18126
18127    let mut cx = EditorTestContext::new(cx).await;
18128
18129    let diff_base = r#"
18130        use some::mod1;
18131        use some::mod2;
18132
18133        const A: u32 = 42;
18134        const B: u32 = 42;
18135        const C: u32 = 42;
18136        const D: u32 = 42;
18137
18138
18139        fn main() {
18140            println!("hello");
18141
18142            println!("world");
18143        }"#
18144    .unindent();
18145
18146    cx.set_state(
18147        &r#"
18148        use some::mod1;
18149        use some::mod2;
18150
18151        const A: u32 = 42;
18152        const B: u32 = 42;
18153        const C: u32 = 43ˇ
18154        const D: u32 = 42;
18155
18156
18157        fn main() {
18158            println!("hello");
18159
18160            println!("world");
18161        }"#
18162        .unindent(),
18163    );
18164
18165    cx.set_head_text(&diff_base);
18166    executor.run_until_parked();
18167    cx.update_editor(|editor, window, cx| {
18168        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18169    });
18170    executor.run_until_parked();
18171
18172    cx.assert_state_with_diff(
18173        r#"
18174        use some::mod1;
18175        use some::mod2;
18176
18177        const A: u32 = 42;
18178        const B: u32 = 42;
18179      - const C: u32 = 42;
18180      + const C: u32 = 43ˇ
18181        const D: u32 = 42;
18182
18183
18184        fn main() {
18185            println!("hello");
18186
18187            println!("world");
18188        }"#
18189        .unindent(),
18190    );
18191
18192    cx.update_editor(|editor, window, cx| {
18193        editor.handle_input("\nnew_line\n", window, cx);
18194    });
18195    executor.run_until_parked();
18196
18197    cx.assert_state_with_diff(
18198        r#"
18199        use some::mod1;
18200        use some::mod2;
18201
18202        const A: u32 = 42;
18203        const B: u32 = 42;
18204      - const C: u32 = 42;
18205      + const C: u32 = 43
18206      + new_line
18207      + ˇ
18208        const D: u32 = 42;
18209
18210
18211        fn main() {
18212            println!("hello");
18213
18214            println!("world");
18215        }"#
18216        .unindent(),
18217    );
18218}
18219
18220#[gpui::test]
18221async fn test_stage_and_unstage_added_file_hunk(
18222    executor: BackgroundExecutor,
18223    cx: &mut TestAppContext,
18224) {
18225    init_test(cx, |_| {});
18226
18227    let mut cx = EditorTestContext::new(cx).await;
18228    cx.update_editor(|editor, _, cx| {
18229        editor.set_expand_all_diff_hunks(cx);
18230    });
18231
18232    let working_copy = r#"
18233            ˇfn main() {
18234                println!("hello, world!");
18235            }
18236        "#
18237    .unindent();
18238
18239    cx.set_state(&working_copy);
18240    executor.run_until_parked();
18241
18242    cx.assert_state_with_diff(
18243        r#"
18244            + ˇfn main() {
18245            +     println!("hello, world!");
18246            + }
18247        "#
18248        .unindent(),
18249    );
18250    cx.assert_index_text(None);
18251
18252    cx.update_editor(|editor, window, cx| {
18253        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18254    });
18255    executor.run_until_parked();
18256    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18257    cx.assert_state_with_diff(
18258        r#"
18259            + ˇfn main() {
18260            +     println!("hello, world!");
18261            + }
18262        "#
18263        .unindent(),
18264    );
18265
18266    cx.update_editor(|editor, window, cx| {
18267        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18268    });
18269    executor.run_until_parked();
18270    cx.assert_index_text(None);
18271}
18272
18273async fn setup_indent_guides_editor(
18274    text: &str,
18275    cx: &mut TestAppContext,
18276) -> (BufferId, EditorTestContext) {
18277    init_test(cx, |_| {});
18278
18279    let mut cx = EditorTestContext::new(cx).await;
18280
18281    let buffer_id = cx.update_editor(|editor, window, cx| {
18282        editor.set_text(text, window, cx);
18283        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18284
18285        buffer_ids[0]
18286    });
18287
18288    (buffer_id, cx)
18289}
18290
18291fn assert_indent_guides(
18292    range: Range<u32>,
18293    expected: Vec<IndentGuide>,
18294    active_indices: Option<Vec<usize>>,
18295    cx: &mut EditorTestContext,
18296) {
18297    let indent_guides = cx.update_editor(|editor, window, cx| {
18298        let snapshot = editor.snapshot(window, cx).display_snapshot;
18299        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18300            editor,
18301            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18302            true,
18303            &snapshot,
18304            cx,
18305        );
18306
18307        indent_guides.sort_by(|a, b| {
18308            a.depth.cmp(&b.depth).then(
18309                a.start_row
18310                    .cmp(&b.start_row)
18311                    .then(a.end_row.cmp(&b.end_row)),
18312            )
18313        });
18314        indent_guides
18315    });
18316
18317    if let Some(expected) = active_indices {
18318        let active_indices = cx.update_editor(|editor, window, cx| {
18319            let snapshot = editor.snapshot(window, cx).display_snapshot;
18320            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18321        });
18322
18323        assert_eq!(
18324            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18325            expected,
18326            "Active indent guide indices do not match"
18327        );
18328    }
18329
18330    assert_eq!(indent_guides, expected, "Indent guides do not match");
18331}
18332
18333fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18334    IndentGuide {
18335        buffer_id,
18336        start_row: MultiBufferRow(start_row),
18337        end_row: MultiBufferRow(end_row),
18338        depth,
18339        tab_size: 4,
18340        settings: IndentGuideSettings {
18341            enabled: true,
18342            line_width: 1,
18343            active_line_width: 1,
18344            ..Default::default()
18345        },
18346    }
18347}
18348
18349#[gpui::test]
18350async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18351    let (buffer_id, mut cx) = setup_indent_guides_editor(
18352        &"
18353        fn main() {
18354            let a = 1;
18355        }"
18356        .unindent(),
18357        cx,
18358    )
18359    .await;
18360
18361    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18362}
18363
18364#[gpui::test]
18365async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18366    let (buffer_id, mut cx) = setup_indent_guides_editor(
18367        &"
18368        fn main() {
18369            let a = 1;
18370            let b = 2;
18371        }"
18372        .unindent(),
18373        cx,
18374    )
18375    .await;
18376
18377    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18378}
18379
18380#[gpui::test]
18381async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18382    let (buffer_id, mut cx) = setup_indent_guides_editor(
18383        &"
18384        fn main() {
18385            let a = 1;
18386            if a == 3 {
18387                let b = 2;
18388            } else {
18389                let c = 3;
18390            }
18391        }"
18392        .unindent(),
18393        cx,
18394    )
18395    .await;
18396
18397    assert_indent_guides(
18398        0..8,
18399        vec![
18400            indent_guide(buffer_id, 1, 6, 0),
18401            indent_guide(buffer_id, 3, 3, 1),
18402            indent_guide(buffer_id, 5, 5, 1),
18403        ],
18404        None,
18405        &mut cx,
18406    );
18407}
18408
18409#[gpui::test]
18410async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18411    let (buffer_id, mut cx) = setup_indent_guides_editor(
18412        &"
18413        fn main() {
18414            let a = 1;
18415                let b = 2;
18416            let c = 3;
18417        }"
18418        .unindent(),
18419        cx,
18420    )
18421    .await;
18422
18423    assert_indent_guides(
18424        0..5,
18425        vec![
18426            indent_guide(buffer_id, 1, 3, 0),
18427            indent_guide(buffer_id, 2, 2, 1),
18428        ],
18429        None,
18430        &mut cx,
18431    );
18432}
18433
18434#[gpui::test]
18435async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18436    let (buffer_id, mut cx) = setup_indent_guides_editor(
18437        &"
18438        fn main() {
18439            let a = 1;
18440
18441            let c = 3;
18442        }"
18443        .unindent(),
18444        cx,
18445    )
18446    .await;
18447
18448    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18449}
18450
18451#[gpui::test]
18452async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18453    let (buffer_id, mut cx) = setup_indent_guides_editor(
18454        &"
18455        fn main() {
18456            let a = 1;
18457
18458            let c = 3;
18459
18460            if a == 3 {
18461                let b = 2;
18462            } else {
18463                let c = 3;
18464            }
18465        }"
18466        .unindent(),
18467        cx,
18468    )
18469    .await;
18470
18471    assert_indent_guides(
18472        0..11,
18473        vec![
18474            indent_guide(buffer_id, 1, 9, 0),
18475            indent_guide(buffer_id, 6, 6, 1),
18476            indent_guide(buffer_id, 8, 8, 1),
18477        ],
18478        None,
18479        &mut cx,
18480    );
18481}
18482
18483#[gpui::test]
18484async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18485    let (buffer_id, mut cx) = setup_indent_guides_editor(
18486        &"
18487        fn main() {
18488            let a = 1;
18489
18490            let c = 3;
18491
18492            if a == 3 {
18493                let b = 2;
18494            } else {
18495                let c = 3;
18496            }
18497        }"
18498        .unindent(),
18499        cx,
18500    )
18501    .await;
18502
18503    assert_indent_guides(
18504        1..11,
18505        vec![
18506            indent_guide(buffer_id, 1, 9, 0),
18507            indent_guide(buffer_id, 6, 6, 1),
18508            indent_guide(buffer_id, 8, 8, 1),
18509        ],
18510        None,
18511        &mut cx,
18512    );
18513}
18514
18515#[gpui::test]
18516async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18517    let (buffer_id, mut cx) = setup_indent_guides_editor(
18518        &"
18519        fn main() {
18520            let a = 1;
18521
18522            let c = 3;
18523
18524            if a == 3 {
18525                let b = 2;
18526            } else {
18527                let c = 3;
18528            }
18529        }"
18530        .unindent(),
18531        cx,
18532    )
18533    .await;
18534
18535    assert_indent_guides(
18536        1..10,
18537        vec![
18538            indent_guide(buffer_id, 1, 9, 0),
18539            indent_guide(buffer_id, 6, 6, 1),
18540            indent_guide(buffer_id, 8, 8, 1),
18541        ],
18542        None,
18543        &mut cx,
18544    );
18545}
18546
18547#[gpui::test]
18548async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18549    let (buffer_id, mut cx) = setup_indent_guides_editor(
18550        &"
18551        fn main() {
18552            if a {
18553                b(
18554                    c,
18555                    d,
18556                )
18557            } else {
18558                e(
18559                    f
18560                )
18561            }
18562        }"
18563        .unindent(),
18564        cx,
18565    )
18566    .await;
18567
18568    assert_indent_guides(
18569        0..11,
18570        vec![
18571            indent_guide(buffer_id, 1, 10, 0),
18572            indent_guide(buffer_id, 2, 5, 1),
18573            indent_guide(buffer_id, 7, 9, 1),
18574            indent_guide(buffer_id, 3, 4, 2),
18575            indent_guide(buffer_id, 8, 8, 2),
18576        ],
18577        None,
18578        &mut cx,
18579    );
18580
18581    cx.update_editor(|editor, window, cx| {
18582        editor.fold_at(MultiBufferRow(2), window, cx);
18583        assert_eq!(
18584            editor.display_text(cx),
18585            "
18586            fn main() {
18587                if a {
18588                    b(⋯
18589                    )
18590                } else {
18591                    e(
18592                        f
18593                    )
18594                }
18595            }"
18596            .unindent()
18597        );
18598    });
18599
18600    assert_indent_guides(
18601        0..11,
18602        vec![
18603            indent_guide(buffer_id, 1, 10, 0),
18604            indent_guide(buffer_id, 2, 5, 1),
18605            indent_guide(buffer_id, 7, 9, 1),
18606            indent_guide(buffer_id, 8, 8, 2),
18607        ],
18608        None,
18609        &mut cx,
18610    );
18611}
18612
18613#[gpui::test]
18614async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18615    let (buffer_id, mut cx) = setup_indent_guides_editor(
18616        &"
18617        block1
18618            block2
18619                block3
18620                    block4
18621            block2
18622        block1
18623        block1"
18624            .unindent(),
18625        cx,
18626    )
18627    .await;
18628
18629    assert_indent_guides(
18630        1..10,
18631        vec![
18632            indent_guide(buffer_id, 1, 4, 0),
18633            indent_guide(buffer_id, 2, 3, 1),
18634            indent_guide(buffer_id, 3, 3, 2),
18635        ],
18636        None,
18637        &mut cx,
18638    );
18639}
18640
18641#[gpui::test]
18642async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18643    let (buffer_id, mut cx) = setup_indent_guides_editor(
18644        &"
18645        block1
18646            block2
18647                block3
18648
18649        block1
18650        block1"
18651            .unindent(),
18652        cx,
18653    )
18654    .await;
18655
18656    assert_indent_guides(
18657        0..6,
18658        vec![
18659            indent_guide(buffer_id, 1, 2, 0),
18660            indent_guide(buffer_id, 2, 2, 1),
18661        ],
18662        None,
18663        &mut cx,
18664    );
18665}
18666
18667#[gpui::test]
18668async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18669    let (buffer_id, mut cx) = setup_indent_guides_editor(
18670        &"
18671        function component() {
18672        \treturn (
18673        \t\t\t
18674        \t\t<div>
18675        \t\t\t<abc></abc>
18676        \t\t</div>
18677        \t)
18678        }"
18679        .unindent(),
18680        cx,
18681    )
18682    .await;
18683
18684    assert_indent_guides(
18685        0..8,
18686        vec![
18687            indent_guide(buffer_id, 1, 6, 0),
18688            indent_guide(buffer_id, 2, 5, 1),
18689            indent_guide(buffer_id, 4, 4, 2),
18690        ],
18691        None,
18692        &mut cx,
18693    );
18694}
18695
18696#[gpui::test]
18697async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18698    let (buffer_id, mut cx) = setup_indent_guides_editor(
18699        &"
18700        function component() {
18701        \treturn (
18702        \t
18703        \t\t<div>
18704        \t\t\t<abc></abc>
18705        \t\t</div>
18706        \t)
18707        }"
18708        .unindent(),
18709        cx,
18710    )
18711    .await;
18712
18713    assert_indent_guides(
18714        0..8,
18715        vec![
18716            indent_guide(buffer_id, 1, 6, 0),
18717            indent_guide(buffer_id, 2, 5, 1),
18718            indent_guide(buffer_id, 4, 4, 2),
18719        ],
18720        None,
18721        &mut cx,
18722    );
18723}
18724
18725#[gpui::test]
18726async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18727    let (buffer_id, mut cx) = setup_indent_guides_editor(
18728        &"
18729        block1
18730
18731
18732
18733            block2
18734        "
18735        .unindent(),
18736        cx,
18737    )
18738    .await;
18739
18740    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18741}
18742
18743#[gpui::test]
18744async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18745    let (buffer_id, mut cx) = setup_indent_guides_editor(
18746        &"
18747        def a:
18748        \tb = 3
18749        \tif True:
18750        \t\tc = 4
18751        \t\td = 5
18752        \tprint(b)
18753        "
18754        .unindent(),
18755        cx,
18756    )
18757    .await;
18758
18759    assert_indent_guides(
18760        0..6,
18761        vec![
18762            indent_guide(buffer_id, 1, 5, 0),
18763            indent_guide(buffer_id, 3, 4, 1),
18764        ],
18765        None,
18766        &mut cx,
18767    );
18768}
18769
18770#[gpui::test]
18771async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18772    let (buffer_id, mut cx) = setup_indent_guides_editor(
18773        &"
18774    fn main() {
18775        let a = 1;
18776    }"
18777        .unindent(),
18778        cx,
18779    )
18780    .await;
18781
18782    cx.update_editor(|editor, window, cx| {
18783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18784            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18785        });
18786    });
18787
18788    assert_indent_guides(
18789        0..3,
18790        vec![indent_guide(buffer_id, 1, 1, 0)],
18791        Some(vec![0]),
18792        &mut cx,
18793    );
18794}
18795
18796#[gpui::test]
18797async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18798    let (buffer_id, mut cx) = setup_indent_guides_editor(
18799        &"
18800    fn main() {
18801        if 1 == 2 {
18802            let a = 1;
18803        }
18804    }"
18805        .unindent(),
18806        cx,
18807    )
18808    .await;
18809
18810    cx.update_editor(|editor, window, cx| {
18811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18812            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18813        });
18814    });
18815
18816    assert_indent_guides(
18817        0..4,
18818        vec![
18819            indent_guide(buffer_id, 1, 3, 0),
18820            indent_guide(buffer_id, 2, 2, 1),
18821        ],
18822        Some(vec![1]),
18823        &mut cx,
18824    );
18825
18826    cx.update_editor(|editor, window, cx| {
18827        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18828            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18829        });
18830    });
18831
18832    assert_indent_guides(
18833        0..4,
18834        vec![
18835            indent_guide(buffer_id, 1, 3, 0),
18836            indent_guide(buffer_id, 2, 2, 1),
18837        ],
18838        Some(vec![1]),
18839        &mut cx,
18840    );
18841
18842    cx.update_editor(|editor, window, cx| {
18843        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18844            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18845        });
18846    });
18847
18848    assert_indent_guides(
18849        0..4,
18850        vec![
18851            indent_guide(buffer_id, 1, 3, 0),
18852            indent_guide(buffer_id, 2, 2, 1),
18853        ],
18854        Some(vec![0]),
18855        &mut cx,
18856    );
18857}
18858
18859#[gpui::test]
18860async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18861    let (buffer_id, mut cx) = setup_indent_guides_editor(
18862        &"
18863    fn main() {
18864        let a = 1;
18865
18866        let b = 2;
18867    }"
18868        .unindent(),
18869        cx,
18870    )
18871    .await;
18872
18873    cx.update_editor(|editor, window, cx| {
18874        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18875            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18876        });
18877    });
18878
18879    assert_indent_guides(
18880        0..5,
18881        vec![indent_guide(buffer_id, 1, 3, 0)],
18882        Some(vec![0]),
18883        &mut cx,
18884    );
18885}
18886
18887#[gpui::test]
18888async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18889    let (buffer_id, mut cx) = setup_indent_guides_editor(
18890        &"
18891    def m:
18892        a = 1
18893        pass"
18894            .unindent(),
18895        cx,
18896    )
18897    .await;
18898
18899    cx.update_editor(|editor, window, cx| {
18900        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18901            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18902        });
18903    });
18904
18905    assert_indent_guides(
18906        0..3,
18907        vec![indent_guide(buffer_id, 1, 2, 0)],
18908        Some(vec![0]),
18909        &mut cx,
18910    );
18911}
18912
18913#[gpui::test]
18914async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18915    init_test(cx, |_| {});
18916    let mut cx = EditorTestContext::new(cx).await;
18917    let text = indoc! {
18918        "
18919        impl A {
18920            fn b() {
18921                0;
18922                3;
18923                5;
18924                6;
18925                7;
18926            }
18927        }
18928        "
18929    };
18930    let base_text = indoc! {
18931        "
18932        impl A {
18933            fn b() {
18934                0;
18935                1;
18936                2;
18937                3;
18938                4;
18939            }
18940            fn c() {
18941                5;
18942                6;
18943                7;
18944            }
18945        }
18946        "
18947    };
18948
18949    cx.update_editor(|editor, window, cx| {
18950        editor.set_text(text, window, cx);
18951
18952        editor.buffer().update(cx, |multibuffer, cx| {
18953            let buffer = multibuffer.as_singleton().unwrap();
18954            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18955
18956            multibuffer.set_all_diff_hunks_expanded(cx);
18957            multibuffer.add_diff(diff, cx);
18958
18959            buffer.read(cx).remote_id()
18960        })
18961    });
18962    cx.run_until_parked();
18963
18964    cx.assert_state_with_diff(
18965        indoc! { "
18966          impl A {
18967              fn b() {
18968                  0;
18969        -         1;
18970        -         2;
18971                  3;
18972        -         4;
18973        -     }
18974        -     fn c() {
18975                  5;
18976                  6;
18977                  7;
18978              }
18979          }
18980          ˇ"
18981        }
18982        .to_string(),
18983    );
18984
18985    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18986        editor
18987            .snapshot(window, cx)
18988            .buffer_snapshot
18989            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18990            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18991            .collect::<Vec<_>>()
18992    });
18993    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18994    assert_eq!(
18995        actual_guides,
18996        vec![
18997            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18998            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18999            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19000        ]
19001    );
19002}
19003
19004#[gpui::test]
19005async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19006    init_test(cx, |_| {});
19007    let mut cx = EditorTestContext::new(cx).await;
19008
19009    let diff_base = r#"
19010        a
19011        b
19012        c
19013        "#
19014    .unindent();
19015
19016    cx.set_state(
19017        &r#"
19018        ˇA
19019        b
19020        C
19021        "#
19022        .unindent(),
19023    );
19024    cx.set_head_text(&diff_base);
19025    cx.update_editor(|editor, window, cx| {
19026        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19027    });
19028    executor.run_until_parked();
19029
19030    let both_hunks_expanded = r#"
19031        - a
19032        + ˇA
19033          b
19034        - c
19035        + C
19036        "#
19037    .unindent();
19038
19039    cx.assert_state_with_diff(both_hunks_expanded.clone());
19040
19041    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19042        let snapshot = editor.snapshot(window, cx);
19043        let hunks = editor
19044            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19045            .collect::<Vec<_>>();
19046        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19047        let buffer_id = hunks[0].buffer_id;
19048        hunks
19049            .into_iter()
19050            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19051            .collect::<Vec<_>>()
19052    });
19053    assert_eq!(hunk_ranges.len(), 2);
19054
19055    cx.update_editor(|editor, _, cx| {
19056        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19057    });
19058    executor.run_until_parked();
19059
19060    let second_hunk_expanded = r#"
19061          ˇA
19062          b
19063        - c
19064        + C
19065        "#
19066    .unindent();
19067
19068    cx.assert_state_with_diff(second_hunk_expanded);
19069
19070    cx.update_editor(|editor, _, cx| {
19071        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19072    });
19073    executor.run_until_parked();
19074
19075    cx.assert_state_with_diff(both_hunks_expanded.clone());
19076
19077    cx.update_editor(|editor, _, cx| {
19078        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19079    });
19080    executor.run_until_parked();
19081
19082    let first_hunk_expanded = r#"
19083        - a
19084        + ˇA
19085          b
19086          C
19087        "#
19088    .unindent();
19089
19090    cx.assert_state_with_diff(first_hunk_expanded);
19091
19092    cx.update_editor(|editor, _, cx| {
19093        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19094    });
19095    executor.run_until_parked();
19096
19097    cx.assert_state_with_diff(both_hunks_expanded);
19098
19099    cx.set_state(
19100        &r#"
19101        ˇA
19102        b
19103        "#
19104        .unindent(),
19105    );
19106    cx.run_until_parked();
19107
19108    // TODO this cursor position seems bad
19109    cx.assert_state_with_diff(
19110        r#"
19111        - ˇa
19112        + A
19113          b
19114        "#
19115        .unindent(),
19116    );
19117
19118    cx.update_editor(|editor, window, cx| {
19119        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19120    });
19121
19122    cx.assert_state_with_diff(
19123        r#"
19124            - ˇa
19125            + A
19126              b
19127            - c
19128            "#
19129        .unindent(),
19130    );
19131
19132    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19133        let snapshot = editor.snapshot(window, cx);
19134        let hunks = editor
19135            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19136            .collect::<Vec<_>>();
19137        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19138        let buffer_id = hunks[0].buffer_id;
19139        hunks
19140            .into_iter()
19141            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19142            .collect::<Vec<_>>()
19143    });
19144    assert_eq!(hunk_ranges.len(), 2);
19145
19146    cx.update_editor(|editor, _, cx| {
19147        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19148    });
19149    executor.run_until_parked();
19150
19151    cx.assert_state_with_diff(
19152        r#"
19153        - ˇa
19154        + A
19155          b
19156        "#
19157        .unindent(),
19158    );
19159}
19160
19161#[gpui::test]
19162async fn test_toggle_deletion_hunk_at_start_of_file(
19163    executor: BackgroundExecutor,
19164    cx: &mut TestAppContext,
19165) {
19166    init_test(cx, |_| {});
19167    let mut cx = EditorTestContext::new(cx).await;
19168
19169    let diff_base = r#"
19170        a
19171        b
19172        c
19173        "#
19174    .unindent();
19175
19176    cx.set_state(
19177        &r#"
19178        ˇb
19179        c
19180        "#
19181        .unindent(),
19182    );
19183    cx.set_head_text(&diff_base);
19184    cx.update_editor(|editor, window, cx| {
19185        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19186    });
19187    executor.run_until_parked();
19188
19189    let hunk_expanded = r#"
19190        - a
19191          ˇb
19192          c
19193        "#
19194    .unindent();
19195
19196    cx.assert_state_with_diff(hunk_expanded.clone());
19197
19198    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19199        let snapshot = editor.snapshot(window, cx);
19200        let hunks = editor
19201            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19202            .collect::<Vec<_>>();
19203        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19204        let buffer_id = hunks[0].buffer_id;
19205        hunks
19206            .into_iter()
19207            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19208            .collect::<Vec<_>>()
19209    });
19210    assert_eq!(hunk_ranges.len(), 1);
19211
19212    cx.update_editor(|editor, _, cx| {
19213        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19214    });
19215    executor.run_until_parked();
19216
19217    let hunk_collapsed = r#"
19218          ˇb
19219          c
19220        "#
19221    .unindent();
19222
19223    cx.assert_state_with_diff(hunk_collapsed);
19224
19225    cx.update_editor(|editor, _, cx| {
19226        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19227    });
19228    executor.run_until_parked();
19229
19230    cx.assert_state_with_diff(hunk_expanded.clone());
19231}
19232
19233#[gpui::test]
19234async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19235    init_test(cx, |_| {});
19236
19237    let fs = FakeFs::new(cx.executor());
19238    fs.insert_tree(
19239        path!("/test"),
19240        json!({
19241            ".git": {},
19242            "file-1": "ONE\n",
19243            "file-2": "TWO\n",
19244            "file-3": "THREE\n",
19245        }),
19246    )
19247    .await;
19248
19249    fs.set_head_for_repo(
19250        path!("/test/.git").as_ref(),
19251        &[
19252            ("file-1".into(), "one\n".into()),
19253            ("file-2".into(), "two\n".into()),
19254            ("file-3".into(), "three\n".into()),
19255        ],
19256        "deadbeef",
19257    );
19258
19259    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19260    let mut buffers = vec![];
19261    for i in 1..=3 {
19262        let buffer = project
19263            .update(cx, |project, cx| {
19264                let path = format!(path!("/test/file-{}"), i);
19265                project.open_local_buffer(path, cx)
19266            })
19267            .await
19268            .unwrap();
19269        buffers.push(buffer);
19270    }
19271
19272    let multibuffer = cx.new(|cx| {
19273        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19274        multibuffer.set_all_diff_hunks_expanded(cx);
19275        for buffer in &buffers {
19276            let snapshot = buffer.read(cx).snapshot();
19277            multibuffer.set_excerpts_for_path(
19278                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19279                buffer.clone(),
19280                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19281                DEFAULT_MULTIBUFFER_CONTEXT,
19282                cx,
19283            );
19284        }
19285        multibuffer
19286    });
19287
19288    let editor = cx.add_window(|window, cx| {
19289        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19290    });
19291    cx.run_until_parked();
19292
19293    let snapshot = editor
19294        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19295        .unwrap();
19296    let hunks = snapshot
19297        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19298        .map(|hunk| match hunk {
19299            DisplayDiffHunk::Unfolded {
19300                display_row_range, ..
19301            } => display_row_range,
19302            DisplayDiffHunk::Folded { .. } => unreachable!(),
19303        })
19304        .collect::<Vec<_>>();
19305    assert_eq!(
19306        hunks,
19307        [
19308            DisplayRow(2)..DisplayRow(4),
19309            DisplayRow(7)..DisplayRow(9),
19310            DisplayRow(12)..DisplayRow(14),
19311        ]
19312    );
19313}
19314
19315#[gpui::test]
19316async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19317    init_test(cx, |_| {});
19318
19319    let mut cx = EditorTestContext::new(cx).await;
19320    cx.set_head_text(indoc! { "
19321        one
19322        two
19323        three
19324        four
19325        five
19326        "
19327    });
19328    cx.set_index_text(indoc! { "
19329        one
19330        two
19331        three
19332        four
19333        five
19334        "
19335    });
19336    cx.set_state(indoc! {"
19337        one
19338        TWO
19339        ˇTHREE
19340        FOUR
19341        five
19342    "});
19343    cx.run_until_parked();
19344    cx.update_editor(|editor, window, cx| {
19345        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19346    });
19347    cx.run_until_parked();
19348    cx.assert_index_text(Some(indoc! {"
19349        one
19350        TWO
19351        THREE
19352        FOUR
19353        five
19354    "}));
19355    cx.set_state(indoc! { "
19356        one
19357        TWO
19358        ˇTHREE-HUNDRED
19359        FOUR
19360        five
19361    "});
19362    cx.run_until_parked();
19363    cx.update_editor(|editor, window, cx| {
19364        let snapshot = editor.snapshot(window, cx);
19365        let hunks = editor
19366            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19367            .collect::<Vec<_>>();
19368        assert_eq!(hunks.len(), 1);
19369        assert_eq!(
19370            hunks[0].status(),
19371            DiffHunkStatus {
19372                kind: DiffHunkStatusKind::Modified,
19373                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19374            }
19375        );
19376
19377        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19378    });
19379    cx.run_until_parked();
19380    cx.assert_index_text(Some(indoc! {"
19381        one
19382        TWO
19383        THREE-HUNDRED
19384        FOUR
19385        five
19386    "}));
19387}
19388
19389#[gpui::test]
19390fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19391    init_test(cx, |_| {});
19392
19393    let editor = cx.add_window(|window, cx| {
19394        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19395        build_editor(buffer, window, cx)
19396    });
19397
19398    let render_args = Arc::new(Mutex::new(None));
19399    let snapshot = editor
19400        .update(cx, |editor, window, cx| {
19401            let snapshot = editor.buffer().read(cx).snapshot(cx);
19402            let range =
19403                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19404
19405            struct RenderArgs {
19406                row: MultiBufferRow,
19407                folded: bool,
19408                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19409            }
19410
19411            let crease = Crease::inline(
19412                range,
19413                FoldPlaceholder::test(),
19414                {
19415                    let toggle_callback = render_args.clone();
19416                    move |row, folded, callback, _window, _cx| {
19417                        *toggle_callback.lock() = Some(RenderArgs {
19418                            row,
19419                            folded,
19420                            callback,
19421                        });
19422                        div()
19423                    }
19424                },
19425                |_row, _folded, _window, _cx| div(),
19426            );
19427
19428            editor.insert_creases(Some(crease), cx);
19429            let snapshot = editor.snapshot(window, cx);
19430            let _div = snapshot.render_crease_toggle(
19431                MultiBufferRow(1),
19432                false,
19433                cx.entity().clone(),
19434                window,
19435                cx,
19436            );
19437            snapshot
19438        })
19439        .unwrap();
19440
19441    let render_args = render_args.lock().take().unwrap();
19442    assert_eq!(render_args.row, MultiBufferRow(1));
19443    assert!(!render_args.folded);
19444    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19445
19446    cx.update_window(*editor, |_, window, cx| {
19447        (render_args.callback)(true, window, cx)
19448    })
19449    .unwrap();
19450    let snapshot = editor
19451        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19452        .unwrap();
19453    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19454
19455    cx.update_window(*editor, |_, window, cx| {
19456        (render_args.callback)(false, window, cx)
19457    })
19458    .unwrap();
19459    let snapshot = editor
19460        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19461        .unwrap();
19462    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19463}
19464
19465#[gpui::test]
19466async fn test_input_text(cx: &mut TestAppContext) {
19467    init_test(cx, |_| {});
19468    let mut cx = EditorTestContext::new(cx).await;
19469
19470    cx.set_state(
19471        &r#"ˇone
19472        two
19473
19474        three
19475        fourˇ
19476        five
19477
19478        siˇx"#
19479            .unindent(),
19480    );
19481
19482    cx.dispatch_action(HandleInput(String::new()));
19483    cx.assert_editor_state(
19484        &r#"ˇone
19485        two
19486
19487        three
19488        fourˇ
19489        five
19490
19491        siˇx"#
19492            .unindent(),
19493    );
19494
19495    cx.dispatch_action(HandleInput("AAAA".to_string()));
19496    cx.assert_editor_state(
19497        &r#"AAAAˇone
19498        two
19499
19500        three
19501        fourAAAAˇ
19502        five
19503
19504        siAAAAˇx"#
19505            .unindent(),
19506    );
19507}
19508
19509#[gpui::test]
19510async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19511    init_test(cx, |_| {});
19512
19513    let mut cx = EditorTestContext::new(cx).await;
19514    cx.set_state(
19515        r#"let foo = 1;
19516let foo = 2;
19517let foo = 3;
19518let fooˇ = 4;
19519let foo = 5;
19520let foo = 6;
19521let foo = 7;
19522let foo = 8;
19523let foo = 9;
19524let foo = 10;
19525let foo = 11;
19526let foo = 12;
19527let foo = 13;
19528let foo = 14;
19529let foo = 15;"#,
19530    );
19531
19532    cx.update_editor(|e, window, cx| {
19533        assert_eq!(
19534            e.next_scroll_position,
19535            NextScrollCursorCenterTopBottom::Center,
19536            "Default next scroll direction is center",
19537        );
19538
19539        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19540        assert_eq!(
19541            e.next_scroll_position,
19542            NextScrollCursorCenterTopBottom::Top,
19543            "After center, next scroll direction should be top",
19544        );
19545
19546        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19547        assert_eq!(
19548            e.next_scroll_position,
19549            NextScrollCursorCenterTopBottom::Bottom,
19550            "After top, next scroll direction should be bottom",
19551        );
19552
19553        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19554        assert_eq!(
19555            e.next_scroll_position,
19556            NextScrollCursorCenterTopBottom::Center,
19557            "After bottom, scrolling should start over",
19558        );
19559
19560        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19561        assert_eq!(
19562            e.next_scroll_position,
19563            NextScrollCursorCenterTopBottom::Top,
19564            "Scrolling continues if retriggered fast enough"
19565        );
19566    });
19567
19568    cx.executor()
19569        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19570    cx.executor().run_until_parked();
19571    cx.update_editor(|e, _, _| {
19572        assert_eq!(
19573            e.next_scroll_position,
19574            NextScrollCursorCenterTopBottom::Center,
19575            "If scrolling is not triggered fast enough, it should reset"
19576        );
19577    });
19578}
19579
19580#[gpui::test]
19581async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19582    init_test(cx, |_| {});
19583    let mut cx = EditorLspTestContext::new_rust(
19584        lsp::ServerCapabilities {
19585            definition_provider: Some(lsp::OneOf::Left(true)),
19586            references_provider: Some(lsp::OneOf::Left(true)),
19587            ..lsp::ServerCapabilities::default()
19588        },
19589        cx,
19590    )
19591    .await;
19592
19593    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19594        let go_to_definition = cx
19595            .lsp
19596            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19597                move |params, _| async move {
19598                    if empty_go_to_definition {
19599                        Ok(None)
19600                    } else {
19601                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19602                            uri: params.text_document_position_params.text_document.uri,
19603                            range: lsp::Range::new(
19604                                lsp::Position::new(4, 3),
19605                                lsp::Position::new(4, 6),
19606                            ),
19607                        })))
19608                    }
19609                },
19610            );
19611        let references = cx
19612            .lsp
19613            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19614                Ok(Some(vec![lsp::Location {
19615                    uri: params.text_document_position.text_document.uri,
19616                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19617                }]))
19618            });
19619        (go_to_definition, references)
19620    };
19621
19622    cx.set_state(
19623        &r#"fn one() {
19624            let mut a = ˇtwo();
19625        }
19626
19627        fn two() {}"#
19628            .unindent(),
19629    );
19630    set_up_lsp_handlers(false, &mut cx);
19631    let navigated = cx
19632        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19633        .await
19634        .expect("Failed to navigate to definition");
19635    assert_eq!(
19636        navigated,
19637        Navigated::Yes,
19638        "Should have navigated to definition from the GetDefinition response"
19639    );
19640    cx.assert_editor_state(
19641        &r#"fn one() {
19642            let mut a = two();
19643        }
19644
19645        fn «twoˇ»() {}"#
19646            .unindent(),
19647    );
19648
19649    let editors = cx.update_workspace(|workspace, _, cx| {
19650        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19651    });
19652    cx.update_editor(|_, _, test_editor_cx| {
19653        assert_eq!(
19654            editors.len(),
19655            1,
19656            "Initially, only one, test, editor should be open in the workspace"
19657        );
19658        assert_eq!(
19659            test_editor_cx.entity(),
19660            editors.last().expect("Asserted len is 1").clone()
19661        );
19662    });
19663
19664    set_up_lsp_handlers(true, &mut cx);
19665    let navigated = cx
19666        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19667        .await
19668        .expect("Failed to navigate to lookup references");
19669    assert_eq!(
19670        navigated,
19671        Navigated::Yes,
19672        "Should have navigated to references as a fallback after empty GoToDefinition response"
19673    );
19674    // We should not change the selections in the existing file,
19675    // if opening another milti buffer with the references
19676    cx.assert_editor_state(
19677        &r#"fn one() {
19678            let mut a = two();
19679        }
19680
19681        fn «twoˇ»() {}"#
19682            .unindent(),
19683    );
19684    let editors = cx.update_workspace(|workspace, _, cx| {
19685        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19686    });
19687    cx.update_editor(|_, _, test_editor_cx| {
19688        assert_eq!(
19689            editors.len(),
19690            2,
19691            "After falling back to references search, we open a new editor with the results"
19692        );
19693        let references_fallback_text = editors
19694            .into_iter()
19695            .find(|new_editor| *new_editor != test_editor_cx.entity())
19696            .expect("Should have one non-test editor now")
19697            .read(test_editor_cx)
19698            .text(test_editor_cx);
19699        assert_eq!(
19700            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19701            "Should use the range from the references response and not the GoToDefinition one"
19702        );
19703    });
19704}
19705
19706#[gpui::test]
19707async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19708    init_test(cx, |_| {});
19709    cx.update(|cx| {
19710        let mut editor_settings = EditorSettings::get_global(cx).clone();
19711        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19712        EditorSettings::override_global(editor_settings, cx);
19713    });
19714    let mut cx = EditorLspTestContext::new_rust(
19715        lsp::ServerCapabilities {
19716            definition_provider: Some(lsp::OneOf::Left(true)),
19717            references_provider: Some(lsp::OneOf::Left(true)),
19718            ..lsp::ServerCapabilities::default()
19719        },
19720        cx,
19721    )
19722    .await;
19723    let original_state = r#"fn one() {
19724        let mut a = ˇtwo();
19725    }
19726
19727    fn two() {}"#
19728        .unindent();
19729    cx.set_state(&original_state);
19730
19731    let mut go_to_definition = cx
19732        .lsp
19733        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19734            move |_, _| async move { Ok(None) },
19735        );
19736    let _references = cx
19737        .lsp
19738        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19739            panic!("Should not call for references with no go to definition fallback")
19740        });
19741
19742    let navigated = cx
19743        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19744        .await
19745        .expect("Failed to navigate to lookup references");
19746    go_to_definition
19747        .next()
19748        .await
19749        .expect("Should have called the go_to_definition handler");
19750
19751    assert_eq!(
19752        navigated,
19753        Navigated::No,
19754        "Should have navigated to references as a fallback after empty GoToDefinition response"
19755    );
19756    cx.assert_editor_state(&original_state);
19757    let editors = cx.update_workspace(|workspace, _, cx| {
19758        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19759    });
19760    cx.update_editor(|_, _, _| {
19761        assert_eq!(
19762            editors.len(),
19763            1,
19764            "After unsuccessful fallback, no other editor should have been opened"
19765        );
19766    });
19767}
19768
19769#[gpui::test]
19770async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19771    init_test(cx, |_| {});
19772
19773    let language = Arc::new(Language::new(
19774        LanguageConfig::default(),
19775        Some(tree_sitter_rust::LANGUAGE.into()),
19776    ));
19777
19778    let text = r#"
19779        #[cfg(test)]
19780        mod tests() {
19781            #[test]
19782            fn runnable_1() {
19783                let a = 1;
19784            }
19785
19786            #[test]
19787            fn runnable_2() {
19788                let a = 1;
19789                let b = 2;
19790            }
19791        }
19792    "#
19793    .unindent();
19794
19795    let fs = FakeFs::new(cx.executor());
19796    fs.insert_file("/file.rs", Default::default()).await;
19797
19798    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19799    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19800    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19801    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19802    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19803
19804    let editor = cx.new_window_entity(|window, cx| {
19805        Editor::new(
19806            EditorMode::full(),
19807            multi_buffer,
19808            Some(project.clone()),
19809            window,
19810            cx,
19811        )
19812    });
19813
19814    editor.update_in(cx, |editor, window, cx| {
19815        let snapshot = editor.buffer().read(cx).snapshot(cx);
19816        editor.tasks.insert(
19817            (buffer.read(cx).remote_id(), 3),
19818            RunnableTasks {
19819                templates: vec![],
19820                offset: snapshot.anchor_before(43),
19821                column: 0,
19822                extra_variables: HashMap::default(),
19823                context_range: BufferOffset(43)..BufferOffset(85),
19824            },
19825        );
19826        editor.tasks.insert(
19827            (buffer.read(cx).remote_id(), 8),
19828            RunnableTasks {
19829                templates: vec![],
19830                offset: snapshot.anchor_before(86),
19831                column: 0,
19832                extra_variables: HashMap::default(),
19833                context_range: BufferOffset(86)..BufferOffset(191),
19834            },
19835        );
19836
19837        // Test finding task when cursor is inside function body
19838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19839            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19840        });
19841        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19842        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19843
19844        // Test finding task when cursor is on function name
19845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19846            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19847        });
19848        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19849        assert_eq!(row, 8, "Should find task when cursor is on function name");
19850    });
19851}
19852
19853#[gpui::test]
19854async fn test_folding_buffers(cx: &mut TestAppContext) {
19855    init_test(cx, |_| {});
19856
19857    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19858    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19859    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19860
19861    let fs = FakeFs::new(cx.executor());
19862    fs.insert_tree(
19863        path!("/a"),
19864        json!({
19865            "first.rs": sample_text_1,
19866            "second.rs": sample_text_2,
19867            "third.rs": sample_text_3,
19868        }),
19869    )
19870    .await;
19871    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19872    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19873    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19874    let worktree = project.update(cx, |project, cx| {
19875        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19876        assert_eq!(worktrees.len(), 1);
19877        worktrees.pop().unwrap()
19878    });
19879    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19880
19881    let buffer_1 = project
19882        .update(cx, |project, cx| {
19883            project.open_buffer((worktree_id, "first.rs"), cx)
19884        })
19885        .await
19886        .unwrap();
19887    let buffer_2 = project
19888        .update(cx, |project, cx| {
19889            project.open_buffer((worktree_id, "second.rs"), cx)
19890        })
19891        .await
19892        .unwrap();
19893    let buffer_3 = project
19894        .update(cx, |project, cx| {
19895            project.open_buffer((worktree_id, "third.rs"), cx)
19896        })
19897        .await
19898        .unwrap();
19899
19900    let multi_buffer = cx.new(|cx| {
19901        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19902        multi_buffer.push_excerpts(
19903            buffer_1.clone(),
19904            [
19905                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19906                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19907                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19908            ],
19909            cx,
19910        );
19911        multi_buffer.push_excerpts(
19912            buffer_2.clone(),
19913            [
19914                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19915                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19916                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19917            ],
19918            cx,
19919        );
19920        multi_buffer.push_excerpts(
19921            buffer_3.clone(),
19922            [
19923                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19924                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19925                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19926            ],
19927            cx,
19928        );
19929        multi_buffer
19930    });
19931    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19932        Editor::new(
19933            EditorMode::full(),
19934            multi_buffer.clone(),
19935            Some(project.clone()),
19936            window,
19937            cx,
19938        )
19939    });
19940
19941    assert_eq!(
19942        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19943        "\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",
19944    );
19945
19946    multi_buffer_editor.update(cx, |editor, cx| {
19947        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19948    });
19949    assert_eq!(
19950        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19951        "\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",
19952        "After folding the first buffer, its text should not be displayed"
19953    );
19954
19955    multi_buffer_editor.update(cx, |editor, cx| {
19956        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19957    });
19958    assert_eq!(
19959        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19960        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19961        "After folding the second buffer, its text should not be displayed"
19962    );
19963
19964    multi_buffer_editor.update(cx, |editor, cx| {
19965        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19966    });
19967    assert_eq!(
19968        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19969        "\n\n\n\n\n",
19970        "After folding the third buffer, its text should not be displayed"
19971    );
19972
19973    // Emulate selection inside the fold logic, that should work
19974    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19975        editor
19976            .snapshot(window, cx)
19977            .next_line_boundary(Point::new(0, 4));
19978    });
19979
19980    multi_buffer_editor.update(cx, |editor, cx| {
19981        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19982    });
19983    assert_eq!(
19984        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19985        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19986        "After unfolding the second buffer, its text should be displayed"
19987    );
19988
19989    // Typing inside of buffer 1 causes that buffer to be unfolded.
19990    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19991        assert_eq!(
19992            multi_buffer
19993                .read(cx)
19994                .snapshot(cx)
19995                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19996                .collect::<String>(),
19997            "bbbb"
19998        );
19999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20000            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20001        });
20002        editor.handle_input("B", window, cx);
20003    });
20004
20005    assert_eq!(
20006        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20007        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20008        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20009    );
20010
20011    multi_buffer_editor.update(cx, |editor, cx| {
20012        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20013    });
20014    assert_eq!(
20015        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20016        "\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",
20017        "After unfolding the all buffers, all original text should be displayed"
20018    );
20019}
20020
20021#[gpui::test]
20022async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20023    init_test(cx, |_| {});
20024
20025    let sample_text_1 = "1111\n2222\n3333".to_string();
20026    let sample_text_2 = "4444\n5555\n6666".to_string();
20027    let sample_text_3 = "7777\n8888\n9999".to_string();
20028
20029    let fs = FakeFs::new(cx.executor());
20030    fs.insert_tree(
20031        path!("/a"),
20032        json!({
20033            "first.rs": sample_text_1,
20034            "second.rs": sample_text_2,
20035            "third.rs": sample_text_3,
20036        }),
20037    )
20038    .await;
20039    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20040    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20041    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20042    let worktree = project.update(cx, |project, cx| {
20043        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20044        assert_eq!(worktrees.len(), 1);
20045        worktrees.pop().unwrap()
20046    });
20047    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20048
20049    let buffer_1 = project
20050        .update(cx, |project, cx| {
20051            project.open_buffer((worktree_id, "first.rs"), cx)
20052        })
20053        .await
20054        .unwrap();
20055    let buffer_2 = project
20056        .update(cx, |project, cx| {
20057            project.open_buffer((worktree_id, "second.rs"), cx)
20058        })
20059        .await
20060        .unwrap();
20061    let buffer_3 = project
20062        .update(cx, |project, cx| {
20063            project.open_buffer((worktree_id, "third.rs"), cx)
20064        })
20065        .await
20066        .unwrap();
20067
20068    let multi_buffer = cx.new(|cx| {
20069        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20070        multi_buffer.push_excerpts(
20071            buffer_1.clone(),
20072            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20073            cx,
20074        );
20075        multi_buffer.push_excerpts(
20076            buffer_2.clone(),
20077            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20078            cx,
20079        );
20080        multi_buffer.push_excerpts(
20081            buffer_3.clone(),
20082            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20083            cx,
20084        );
20085        multi_buffer
20086    });
20087
20088    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20089        Editor::new(
20090            EditorMode::full(),
20091            multi_buffer,
20092            Some(project.clone()),
20093            window,
20094            cx,
20095        )
20096    });
20097
20098    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20099    assert_eq!(
20100        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20101        full_text,
20102    );
20103
20104    multi_buffer_editor.update(cx, |editor, cx| {
20105        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20106    });
20107    assert_eq!(
20108        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20109        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20110        "After folding the first buffer, its text should not be displayed"
20111    );
20112
20113    multi_buffer_editor.update(cx, |editor, cx| {
20114        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20115    });
20116
20117    assert_eq!(
20118        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20119        "\n\n\n\n\n\n7777\n8888\n9999",
20120        "After folding the second buffer, its text should not be displayed"
20121    );
20122
20123    multi_buffer_editor.update(cx, |editor, cx| {
20124        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20125    });
20126    assert_eq!(
20127        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20128        "\n\n\n\n\n",
20129        "After folding the third buffer, its text should not be displayed"
20130    );
20131
20132    multi_buffer_editor.update(cx, |editor, cx| {
20133        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20134    });
20135    assert_eq!(
20136        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20137        "\n\n\n\n4444\n5555\n6666\n\n",
20138        "After unfolding the second buffer, its text should be displayed"
20139    );
20140
20141    multi_buffer_editor.update(cx, |editor, cx| {
20142        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20143    });
20144    assert_eq!(
20145        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20146        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20147        "After unfolding the first buffer, its text should be displayed"
20148    );
20149
20150    multi_buffer_editor.update(cx, |editor, cx| {
20151        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20152    });
20153    assert_eq!(
20154        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20155        full_text,
20156        "After unfolding all buffers, all original text should be displayed"
20157    );
20158}
20159
20160#[gpui::test]
20161async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20162    init_test(cx, |_| {});
20163
20164    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20165
20166    let fs = FakeFs::new(cx.executor());
20167    fs.insert_tree(
20168        path!("/a"),
20169        json!({
20170            "main.rs": sample_text,
20171        }),
20172    )
20173    .await;
20174    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20175    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20176    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20177    let worktree = project.update(cx, |project, cx| {
20178        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20179        assert_eq!(worktrees.len(), 1);
20180        worktrees.pop().unwrap()
20181    });
20182    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20183
20184    let buffer_1 = project
20185        .update(cx, |project, cx| {
20186            project.open_buffer((worktree_id, "main.rs"), cx)
20187        })
20188        .await
20189        .unwrap();
20190
20191    let multi_buffer = cx.new(|cx| {
20192        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20193        multi_buffer.push_excerpts(
20194            buffer_1.clone(),
20195            [ExcerptRange::new(
20196                Point::new(0, 0)
20197                    ..Point::new(
20198                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20199                        0,
20200                    ),
20201            )],
20202            cx,
20203        );
20204        multi_buffer
20205    });
20206    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20207        Editor::new(
20208            EditorMode::full(),
20209            multi_buffer,
20210            Some(project.clone()),
20211            window,
20212            cx,
20213        )
20214    });
20215
20216    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20217    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20218        enum TestHighlight {}
20219        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20220        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20221        editor.highlight_text::<TestHighlight>(
20222            vec![highlight_range.clone()],
20223            HighlightStyle::color(Hsla::green()),
20224            cx,
20225        );
20226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20227            s.select_ranges(Some(highlight_range))
20228        });
20229    });
20230
20231    let full_text = format!("\n\n{sample_text}");
20232    assert_eq!(
20233        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20234        full_text,
20235    );
20236}
20237
20238#[gpui::test]
20239async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20240    init_test(cx, |_| {});
20241    cx.update(|cx| {
20242        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20243            "keymaps/default-linux.json",
20244            cx,
20245        )
20246        .unwrap();
20247        cx.bind_keys(default_key_bindings);
20248    });
20249
20250    let (editor, cx) = cx.add_window_view(|window, cx| {
20251        let multi_buffer = MultiBuffer::build_multi(
20252            [
20253                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20254                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20255                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20256                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20257            ],
20258            cx,
20259        );
20260        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20261
20262        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20263        // fold all but the second buffer, so that we test navigating between two
20264        // adjacent folded buffers, as well as folded buffers at the start and
20265        // end the multibuffer
20266        editor.fold_buffer(buffer_ids[0], cx);
20267        editor.fold_buffer(buffer_ids[2], cx);
20268        editor.fold_buffer(buffer_ids[3], cx);
20269
20270        editor
20271    });
20272    cx.simulate_resize(size(px(1000.), px(1000.)));
20273
20274    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20275    cx.assert_excerpts_with_selections(indoc! {"
20276        [EXCERPT]
20277        ˇ[FOLDED]
20278        [EXCERPT]
20279        a1
20280        b1
20281        [EXCERPT]
20282        [FOLDED]
20283        [EXCERPT]
20284        [FOLDED]
20285        "
20286    });
20287    cx.simulate_keystroke("down");
20288    cx.assert_excerpts_with_selections(indoc! {"
20289        [EXCERPT]
20290        [FOLDED]
20291        [EXCERPT]
20292        ˇa1
20293        b1
20294        [EXCERPT]
20295        [FOLDED]
20296        [EXCERPT]
20297        [FOLDED]
20298        "
20299    });
20300    cx.simulate_keystroke("down");
20301    cx.assert_excerpts_with_selections(indoc! {"
20302        [EXCERPT]
20303        [FOLDED]
20304        [EXCERPT]
20305        a1
20306        ˇb1
20307        [EXCERPT]
20308        [FOLDED]
20309        [EXCERPT]
20310        [FOLDED]
20311        "
20312    });
20313    cx.simulate_keystroke("down");
20314    cx.assert_excerpts_with_selections(indoc! {"
20315        [EXCERPT]
20316        [FOLDED]
20317        [EXCERPT]
20318        a1
20319        b1
20320        ˇ[EXCERPT]
20321        [FOLDED]
20322        [EXCERPT]
20323        [FOLDED]
20324        "
20325    });
20326    cx.simulate_keystroke("down");
20327    cx.assert_excerpts_with_selections(indoc! {"
20328        [EXCERPT]
20329        [FOLDED]
20330        [EXCERPT]
20331        a1
20332        b1
20333        [EXCERPT]
20334        ˇ[FOLDED]
20335        [EXCERPT]
20336        [FOLDED]
20337        "
20338    });
20339    for _ in 0..5 {
20340        cx.simulate_keystroke("down");
20341        cx.assert_excerpts_with_selections(indoc! {"
20342            [EXCERPT]
20343            [FOLDED]
20344            [EXCERPT]
20345            a1
20346            b1
20347            [EXCERPT]
20348            [FOLDED]
20349            [EXCERPT]
20350            ˇ[FOLDED]
20351            "
20352        });
20353    }
20354
20355    cx.simulate_keystroke("up");
20356    cx.assert_excerpts_with_selections(indoc! {"
20357        [EXCERPT]
20358        [FOLDED]
20359        [EXCERPT]
20360        a1
20361        b1
20362        [EXCERPT]
20363        ˇ[FOLDED]
20364        [EXCERPT]
20365        [FOLDED]
20366        "
20367    });
20368    cx.simulate_keystroke("up");
20369    cx.assert_excerpts_with_selections(indoc! {"
20370        [EXCERPT]
20371        [FOLDED]
20372        [EXCERPT]
20373        a1
20374        b1
20375        ˇ[EXCERPT]
20376        [FOLDED]
20377        [EXCERPT]
20378        [FOLDED]
20379        "
20380    });
20381    cx.simulate_keystroke("up");
20382    cx.assert_excerpts_with_selections(indoc! {"
20383        [EXCERPT]
20384        [FOLDED]
20385        [EXCERPT]
20386        a1
20387        ˇb1
20388        [EXCERPT]
20389        [FOLDED]
20390        [EXCERPT]
20391        [FOLDED]
20392        "
20393    });
20394    cx.simulate_keystroke("up");
20395    cx.assert_excerpts_with_selections(indoc! {"
20396        [EXCERPT]
20397        [FOLDED]
20398        [EXCERPT]
20399        ˇa1
20400        b1
20401        [EXCERPT]
20402        [FOLDED]
20403        [EXCERPT]
20404        [FOLDED]
20405        "
20406    });
20407    for _ in 0..5 {
20408        cx.simulate_keystroke("up");
20409        cx.assert_excerpts_with_selections(indoc! {"
20410            [EXCERPT]
20411            ˇ[FOLDED]
20412            [EXCERPT]
20413            a1
20414            b1
20415            [EXCERPT]
20416            [FOLDED]
20417            [EXCERPT]
20418            [FOLDED]
20419            "
20420        });
20421    }
20422}
20423
20424#[gpui::test]
20425async fn test_inline_completion_text(cx: &mut TestAppContext) {
20426    init_test(cx, |_| {});
20427
20428    // Simple insertion
20429    assert_highlighted_edits(
20430        "Hello, world!",
20431        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20432        true,
20433        cx,
20434        |highlighted_edits, cx| {
20435            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20436            assert_eq!(highlighted_edits.highlights.len(), 1);
20437            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20438            assert_eq!(
20439                highlighted_edits.highlights[0].1.background_color,
20440                Some(cx.theme().status().created_background)
20441            );
20442        },
20443    )
20444    .await;
20445
20446    // Replacement
20447    assert_highlighted_edits(
20448        "This is a test.",
20449        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20450        false,
20451        cx,
20452        |highlighted_edits, cx| {
20453            assert_eq!(highlighted_edits.text, "That is a test.");
20454            assert_eq!(highlighted_edits.highlights.len(), 1);
20455            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20456            assert_eq!(
20457                highlighted_edits.highlights[0].1.background_color,
20458                Some(cx.theme().status().created_background)
20459            );
20460        },
20461    )
20462    .await;
20463
20464    // Multiple edits
20465    assert_highlighted_edits(
20466        "Hello, world!",
20467        vec![
20468            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20469            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20470        ],
20471        false,
20472        cx,
20473        |highlighted_edits, cx| {
20474            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20475            assert_eq!(highlighted_edits.highlights.len(), 2);
20476            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20477            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20478            assert_eq!(
20479                highlighted_edits.highlights[0].1.background_color,
20480                Some(cx.theme().status().created_background)
20481            );
20482            assert_eq!(
20483                highlighted_edits.highlights[1].1.background_color,
20484                Some(cx.theme().status().created_background)
20485            );
20486        },
20487    )
20488    .await;
20489
20490    // Multiple lines with edits
20491    assert_highlighted_edits(
20492        "First line\nSecond line\nThird line\nFourth line",
20493        vec![
20494            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20495            (
20496                Point::new(2, 0)..Point::new(2, 10),
20497                "New third line".to_string(),
20498            ),
20499            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20500        ],
20501        false,
20502        cx,
20503        |highlighted_edits, cx| {
20504            assert_eq!(
20505                highlighted_edits.text,
20506                "Second modified\nNew third line\nFourth updated line"
20507            );
20508            assert_eq!(highlighted_edits.highlights.len(), 3);
20509            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20510            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20511            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20512            for highlight in &highlighted_edits.highlights {
20513                assert_eq!(
20514                    highlight.1.background_color,
20515                    Some(cx.theme().status().created_background)
20516                );
20517            }
20518        },
20519    )
20520    .await;
20521}
20522
20523#[gpui::test]
20524async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20525    init_test(cx, |_| {});
20526
20527    // Deletion
20528    assert_highlighted_edits(
20529        "Hello, world!",
20530        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20531        true,
20532        cx,
20533        |highlighted_edits, cx| {
20534            assert_eq!(highlighted_edits.text, "Hello, world!");
20535            assert_eq!(highlighted_edits.highlights.len(), 1);
20536            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20537            assert_eq!(
20538                highlighted_edits.highlights[0].1.background_color,
20539                Some(cx.theme().status().deleted_background)
20540            );
20541        },
20542    )
20543    .await;
20544
20545    // Insertion
20546    assert_highlighted_edits(
20547        "Hello, world!",
20548        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20549        true,
20550        cx,
20551        |highlighted_edits, cx| {
20552            assert_eq!(highlighted_edits.highlights.len(), 1);
20553            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20554            assert_eq!(
20555                highlighted_edits.highlights[0].1.background_color,
20556                Some(cx.theme().status().created_background)
20557            );
20558        },
20559    )
20560    .await;
20561}
20562
20563async fn assert_highlighted_edits(
20564    text: &str,
20565    edits: Vec<(Range<Point>, String)>,
20566    include_deletions: bool,
20567    cx: &mut TestAppContext,
20568    assertion_fn: impl Fn(HighlightedText, &App),
20569) {
20570    let window = cx.add_window(|window, cx| {
20571        let buffer = MultiBuffer::build_simple(text, cx);
20572        Editor::new(EditorMode::full(), buffer, None, window, cx)
20573    });
20574    let cx = &mut VisualTestContext::from_window(*window, cx);
20575
20576    let (buffer, snapshot) = window
20577        .update(cx, |editor, _window, cx| {
20578            (
20579                editor.buffer().clone(),
20580                editor.buffer().read(cx).snapshot(cx),
20581            )
20582        })
20583        .unwrap();
20584
20585    let edits = edits
20586        .into_iter()
20587        .map(|(range, edit)| {
20588            (
20589                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20590                edit,
20591            )
20592        })
20593        .collect::<Vec<_>>();
20594
20595    let text_anchor_edits = edits
20596        .clone()
20597        .into_iter()
20598        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20599        .collect::<Vec<_>>();
20600
20601    let edit_preview = window
20602        .update(cx, |_, _window, cx| {
20603            buffer
20604                .read(cx)
20605                .as_singleton()
20606                .unwrap()
20607                .read(cx)
20608                .preview_edits(text_anchor_edits.into(), cx)
20609        })
20610        .unwrap()
20611        .await;
20612
20613    cx.update(|_window, cx| {
20614        let highlighted_edits = inline_completion_edit_text(
20615            &snapshot.as_singleton().unwrap().2,
20616            &edits,
20617            &edit_preview,
20618            include_deletions,
20619            cx,
20620        );
20621        assertion_fn(highlighted_edits, cx)
20622    });
20623}
20624
20625#[track_caller]
20626fn assert_breakpoint(
20627    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20628    path: &Arc<Path>,
20629    expected: Vec<(u32, Breakpoint)>,
20630) {
20631    if expected.len() == 0usize {
20632        assert!(!breakpoints.contains_key(path), "{}", path.display());
20633    } else {
20634        let mut breakpoint = breakpoints
20635            .get(path)
20636            .unwrap()
20637            .into_iter()
20638            .map(|breakpoint| {
20639                (
20640                    breakpoint.row,
20641                    Breakpoint {
20642                        message: breakpoint.message.clone(),
20643                        state: breakpoint.state,
20644                        condition: breakpoint.condition.clone(),
20645                        hit_condition: breakpoint.hit_condition.clone(),
20646                    },
20647                )
20648            })
20649            .collect::<Vec<_>>();
20650
20651        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20652
20653        assert_eq!(expected, breakpoint);
20654    }
20655}
20656
20657fn add_log_breakpoint_at_cursor(
20658    editor: &mut Editor,
20659    log_message: &str,
20660    window: &mut Window,
20661    cx: &mut Context<Editor>,
20662) {
20663    let (anchor, bp) = editor
20664        .breakpoints_at_cursors(window, cx)
20665        .first()
20666        .and_then(|(anchor, bp)| {
20667            if let Some(bp) = bp {
20668                Some((*anchor, bp.clone()))
20669            } else {
20670                None
20671            }
20672        })
20673        .unwrap_or_else(|| {
20674            let cursor_position: Point = editor.selections.newest(cx).head();
20675
20676            let breakpoint_position = editor
20677                .snapshot(window, cx)
20678                .display_snapshot
20679                .buffer_snapshot
20680                .anchor_before(Point::new(cursor_position.row, 0));
20681
20682            (breakpoint_position, Breakpoint::new_log(&log_message))
20683        });
20684
20685    editor.edit_breakpoint_at_anchor(
20686        anchor,
20687        bp,
20688        BreakpointEditAction::EditLogMessage(log_message.into()),
20689        cx,
20690    );
20691}
20692
20693#[gpui::test]
20694async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20695    init_test(cx, |_| {});
20696
20697    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20698    let fs = FakeFs::new(cx.executor());
20699    fs.insert_tree(
20700        path!("/a"),
20701        json!({
20702            "main.rs": sample_text,
20703        }),
20704    )
20705    .await;
20706    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20707    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20708    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20709
20710    let fs = FakeFs::new(cx.executor());
20711    fs.insert_tree(
20712        path!("/a"),
20713        json!({
20714            "main.rs": sample_text,
20715        }),
20716    )
20717    .await;
20718    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20719    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20720    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20721    let worktree_id = workspace
20722        .update(cx, |workspace, _window, cx| {
20723            workspace.project().update(cx, |project, cx| {
20724                project.worktrees(cx).next().unwrap().read(cx).id()
20725            })
20726        })
20727        .unwrap();
20728
20729    let buffer = project
20730        .update(cx, |project, cx| {
20731            project.open_buffer((worktree_id, "main.rs"), cx)
20732        })
20733        .await
20734        .unwrap();
20735
20736    let (editor, cx) = cx.add_window_view(|window, cx| {
20737        Editor::new(
20738            EditorMode::full(),
20739            MultiBuffer::build_from_buffer(buffer, cx),
20740            Some(project.clone()),
20741            window,
20742            cx,
20743        )
20744    });
20745
20746    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20747    let abs_path = project.read_with(cx, |project, cx| {
20748        project
20749            .absolute_path(&project_path, cx)
20750            .map(|path_buf| Arc::from(path_buf.to_owned()))
20751            .unwrap()
20752    });
20753
20754    // assert we can add breakpoint on the first line
20755    editor.update_in(cx, |editor, window, cx| {
20756        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20757        editor.move_to_end(&MoveToEnd, window, cx);
20758        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20759    });
20760
20761    let breakpoints = editor.update(cx, |editor, cx| {
20762        editor
20763            .breakpoint_store()
20764            .as_ref()
20765            .unwrap()
20766            .read(cx)
20767            .all_source_breakpoints(cx)
20768            .clone()
20769    });
20770
20771    assert_eq!(1, breakpoints.len());
20772    assert_breakpoint(
20773        &breakpoints,
20774        &abs_path,
20775        vec![
20776            (0, Breakpoint::new_standard()),
20777            (3, Breakpoint::new_standard()),
20778        ],
20779    );
20780
20781    editor.update_in(cx, |editor, window, cx| {
20782        editor.move_to_beginning(&MoveToBeginning, window, cx);
20783        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20784    });
20785
20786    let breakpoints = editor.update(cx, |editor, cx| {
20787        editor
20788            .breakpoint_store()
20789            .as_ref()
20790            .unwrap()
20791            .read(cx)
20792            .all_source_breakpoints(cx)
20793            .clone()
20794    });
20795
20796    assert_eq!(1, breakpoints.len());
20797    assert_breakpoint(
20798        &breakpoints,
20799        &abs_path,
20800        vec![(3, Breakpoint::new_standard())],
20801    );
20802
20803    editor.update_in(cx, |editor, window, cx| {
20804        editor.move_to_end(&MoveToEnd, window, cx);
20805        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20806    });
20807
20808    let breakpoints = editor.update(cx, |editor, cx| {
20809        editor
20810            .breakpoint_store()
20811            .as_ref()
20812            .unwrap()
20813            .read(cx)
20814            .all_source_breakpoints(cx)
20815            .clone()
20816    });
20817
20818    assert_eq!(0, breakpoints.len());
20819    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20820}
20821
20822#[gpui::test]
20823async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20824    init_test(cx, |_| {});
20825
20826    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20827
20828    let fs = FakeFs::new(cx.executor());
20829    fs.insert_tree(
20830        path!("/a"),
20831        json!({
20832            "main.rs": sample_text,
20833        }),
20834    )
20835    .await;
20836    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20837    let (workspace, cx) =
20838        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20839
20840    let worktree_id = workspace.update(cx, |workspace, cx| {
20841        workspace.project().update(cx, |project, cx| {
20842            project.worktrees(cx).next().unwrap().read(cx).id()
20843        })
20844    });
20845
20846    let buffer = project
20847        .update(cx, |project, cx| {
20848            project.open_buffer((worktree_id, "main.rs"), cx)
20849        })
20850        .await
20851        .unwrap();
20852
20853    let (editor, cx) = cx.add_window_view(|window, cx| {
20854        Editor::new(
20855            EditorMode::full(),
20856            MultiBuffer::build_from_buffer(buffer, cx),
20857            Some(project.clone()),
20858            window,
20859            cx,
20860        )
20861    });
20862
20863    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20864    let abs_path = project.read_with(cx, |project, cx| {
20865        project
20866            .absolute_path(&project_path, cx)
20867            .map(|path_buf| Arc::from(path_buf.to_owned()))
20868            .unwrap()
20869    });
20870
20871    editor.update_in(cx, |editor, window, cx| {
20872        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20873    });
20874
20875    let breakpoints = editor.update(cx, |editor, cx| {
20876        editor
20877            .breakpoint_store()
20878            .as_ref()
20879            .unwrap()
20880            .read(cx)
20881            .all_source_breakpoints(cx)
20882            .clone()
20883    });
20884
20885    assert_breakpoint(
20886        &breakpoints,
20887        &abs_path,
20888        vec![(0, Breakpoint::new_log("hello world"))],
20889    );
20890
20891    // Removing a log message from a log breakpoint should remove it
20892    editor.update_in(cx, |editor, window, cx| {
20893        add_log_breakpoint_at_cursor(editor, "", window, cx);
20894    });
20895
20896    let breakpoints = editor.update(cx, |editor, cx| {
20897        editor
20898            .breakpoint_store()
20899            .as_ref()
20900            .unwrap()
20901            .read(cx)
20902            .all_source_breakpoints(cx)
20903            .clone()
20904    });
20905
20906    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20907
20908    editor.update_in(cx, |editor, window, cx| {
20909        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20910        editor.move_to_end(&MoveToEnd, window, cx);
20911        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20912        // Not adding a log message to a standard breakpoint shouldn't remove it
20913        add_log_breakpoint_at_cursor(editor, "", window, cx);
20914    });
20915
20916    let breakpoints = editor.update(cx, |editor, cx| {
20917        editor
20918            .breakpoint_store()
20919            .as_ref()
20920            .unwrap()
20921            .read(cx)
20922            .all_source_breakpoints(cx)
20923            .clone()
20924    });
20925
20926    assert_breakpoint(
20927        &breakpoints,
20928        &abs_path,
20929        vec![
20930            (0, Breakpoint::new_standard()),
20931            (3, Breakpoint::new_standard()),
20932        ],
20933    );
20934
20935    editor.update_in(cx, |editor, window, cx| {
20936        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20937    });
20938
20939    let breakpoints = editor.update(cx, |editor, cx| {
20940        editor
20941            .breakpoint_store()
20942            .as_ref()
20943            .unwrap()
20944            .read(cx)
20945            .all_source_breakpoints(cx)
20946            .clone()
20947    });
20948
20949    assert_breakpoint(
20950        &breakpoints,
20951        &abs_path,
20952        vec![
20953            (0, Breakpoint::new_standard()),
20954            (3, Breakpoint::new_log("hello world")),
20955        ],
20956    );
20957
20958    editor.update_in(cx, |editor, window, cx| {
20959        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20960    });
20961
20962    let breakpoints = editor.update(cx, |editor, cx| {
20963        editor
20964            .breakpoint_store()
20965            .as_ref()
20966            .unwrap()
20967            .read(cx)
20968            .all_source_breakpoints(cx)
20969            .clone()
20970    });
20971
20972    assert_breakpoint(
20973        &breakpoints,
20974        &abs_path,
20975        vec![
20976            (0, Breakpoint::new_standard()),
20977            (3, Breakpoint::new_log("hello Earth!!")),
20978        ],
20979    );
20980}
20981
20982/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20983/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20984/// or when breakpoints were placed out of order. This tests for a regression too
20985#[gpui::test]
20986async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20987    init_test(cx, |_| {});
20988
20989    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20990    let fs = FakeFs::new(cx.executor());
20991    fs.insert_tree(
20992        path!("/a"),
20993        json!({
20994            "main.rs": sample_text,
20995        }),
20996    )
20997    .await;
20998    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20999    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21000    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21001
21002    let fs = FakeFs::new(cx.executor());
21003    fs.insert_tree(
21004        path!("/a"),
21005        json!({
21006            "main.rs": sample_text,
21007        }),
21008    )
21009    .await;
21010    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21011    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21012    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21013    let worktree_id = workspace
21014        .update(cx, |workspace, _window, cx| {
21015            workspace.project().update(cx, |project, cx| {
21016                project.worktrees(cx).next().unwrap().read(cx).id()
21017            })
21018        })
21019        .unwrap();
21020
21021    let buffer = project
21022        .update(cx, |project, cx| {
21023            project.open_buffer((worktree_id, "main.rs"), cx)
21024        })
21025        .await
21026        .unwrap();
21027
21028    let (editor, cx) = cx.add_window_view(|window, cx| {
21029        Editor::new(
21030            EditorMode::full(),
21031            MultiBuffer::build_from_buffer(buffer, cx),
21032            Some(project.clone()),
21033            window,
21034            cx,
21035        )
21036    });
21037
21038    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21039    let abs_path = project.read_with(cx, |project, cx| {
21040        project
21041            .absolute_path(&project_path, cx)
21042            .map(|path_buf| Arc::from(path_buf.to_owned()))
21043            .unwrap()
21044    });
21045
21046    // assert we can add breakpoint on the first line
21047    editor.update_in(cx, |editor, window, cx| {
21048        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21049        editor.move_to_end(&MoveToEnd, window, cx);
21050        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21051        editor.move_up(&MoveUp, window, cx);
21052        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21053    });
21054
21055    let breakpoints = editor.update(cx, |editor, cx| {
21056        editor
21057            .breakpoint_store()
21058            .as_ref()
21059            .unwrap()
21060            .read(cx)
21061            .all_source_breakpoints(cx)
21062            .clone()
21063    });
21064
21065    assert_eq!(1, breakpoints.len());
21066    assert_breakpoint(
21067        &breakpoints,
21068        &abs_path,
21069        vec![
21070            (0, Breakpoint::new_standard()),
21071            (2, Breakpoint::new_standard()),
21072            (3, Breakpoint::new_standard()),
21073        ],
21074    );
21075
21076    editor.update_in(cx, |editor, window, cx| {
21077        editor.move_to_beginning(&MoveToBeginning, window, cx);
21078        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21079        editor.move_to_end(&MoveToEnd, window, cx);
21080        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21081        // Disabling a breakpoint that doesn't exist should do nothing
21082        editor.move_up(&MoveUp, window, cx);
21083        editor.move_up(&MoveUp, window, cx);
21084        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21085    });
21086
21087    let breakpoints = editor.update(cx, |editor, cx| {
21088        editor
21089            .breakpoint_store()
21090            .as_ref()
21091            .unwrap()
21092            .read(cx)
21093            .all_source_breakpoints(cx)
21094            .clone()
21095    });
21096
21097    let disable_breakpoint = {
21098        let mut bp = Breakpoint::new_standard();
21099        bp.state = BreakpointState::Disabled;
21100        bp
21101    };
21102
21103    assert_eq!(1, breakpoints.len());
21104    assert_breakpoint(
21105        &breakpoints,
21106        &abs_path,
21107        vec![
21108            (0, disable_breakpoint.clone()),
21109            (2, Breakpoint::new_standard()),
21110            (3, disable_breakpoint.clone()),
21111        ],
21112    );
21113
21114    editor.update_in(cx, |editor, window, cx| {
21115        editor.move_to_beginning(&MoveToBeginning, window, cx);
21116        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21117        editor.move_to_end(&MoveToEnd, window, cx);
21118        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21119        editor.move_up(&MoveUp, window, cx);
21120        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21121    });
21122
21123    let breakpoints = editor.update(cx, |editor, cx| {
21124        editor
21125            .breakpoint_store()
21126            .as_ref()
21127            .unwrap()
21128            .read(cx)
21129            .all_source_breakpoints(cx)
21130            .clone()
21131    });
21132
21133    assert_eq!(1, breakpoints.len());
21134    assert_breakpoint(
21135        &breakpoints,
21136        &abs_path,
21137        vec![
21138            (0, Breakpoint::new_standard()),
21139            (2, disable_breakpoint),
21140            (3, Breakpoint::new_standard()),
21141        ],
21142    );
21143}
21144
21145#[gpui::test]
21146async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21147    init_test(cx, |_| {});
21148    let capabilities = lsp::ServerCapabilities {
21149        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21150            prepare_provider: Some(true),
21151            work_done_progress_options: Default::default(),
21152        })),
21153        ..Default::default()
21154    };
21155    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21156
21157    cx.set_state(indoc! {"
21158        struct Fˇoo {}
21159    "});
21160
21161    cx.update_editor(|editor, _, cx| {
21162        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21163        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21164        editor.highlight_background::<DocumentHighlightRead>(
21165            &[highlight_range],
21166            |theme| theme.colors().editor_document_highlight_read_background,
21167            cx,
21168        );
21169    });
21170
21171    let mut prepare_rename_handler = cx
21172        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21173            move |_, _, _| async move {
21174                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21175                    start: lsp::Position {
21176                        line: 0,
21177                        character: 7,
21178                    },
21179                    end: lsp::Position {
21180                        line: 0,
21181                        character: 10,
21182                    },
21183                })))
21184            },
21185        );
21186    let prepare_rename_task = cx
21187        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21188        .expect("Prepare rename was not started");
21189    prepare_rename_handler.next().await.unwrap();
21190    prepare_rename_task.await.expect("Prepare rename failed");
21191
21192    let mut rename_handler =
21193        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21194            let edit = lsp::TextEdit {
21195                range: lsp::Range {
21196                    start: lsp::Position {
21197                        line: 0,
21198                        character: 7,
21199                    },
21200                    end: lsp::Position {
21201                        line: 0,
21202                        character: 10,
21203                    },
21204                },
21205                new_text: "FooRenamed".to_string(),
21206            };
21207            Ok(Some(lsp::WorkspaceEdit::new(
21208                // Specify the same edit twice
21209                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21210            )))
21211        });
21212    let rename_task = cx
21213        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21214        .expect("Confirm rename was not started");
21215    rename_handler.next().await.unwrap();
21216    rename_task.await.expect("Confirm rename failed");
21217    cx.run_until_parked();
21218
21219    // Despite two edits, only one is actually applied as those are identical
21220    cx.assert_editor_state(indoc! {"
21221        struct FooRenamedˇ {}
21222    "});
21223}
21224
21225#[gpui::test]
21226async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21227    init_test(cx, |_| {});
21228    // These capabilities indicate that the server does not support prepare rename.
21229    let capabilities = lsp::ServerCapabilities {
21230        rename_provider: Some(lsp::OneOf::Left(true)),
21231        ..Default::default()
21232    };
21233    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21234
21235    cx.set_state(indoc! {"
21236        struct Fˇoo {}
21237    "});
21238
21239    cx.update_editor(|editor, _window, cx| {
21240        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21241        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21242        editor.highlight_background::<DocumentHighlightRead>(
21243            &[highlight_range],
21244            |theme| theme.colors().editor_document_highlight_read_background,
21245            cx,
21246        );
21247    });
21248
21249    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21250        .expect("Prepare rename was not started")
21251        .await
21252        .expect("Prepare rename failed");
21253
21254    let mut rename_handler =
21255        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21256            let edit = lsp::TextEdit {
21257                range: lsp::Range {
21258                    start: lsp::Position {
21259                        line: 0,
21260                        character: 7,
21261                    },
21262                    end: lsp::Position {
21263                        line: 0,
21264                        character: 10,
21265                    },
21266                },
21267                new_text: "FooRenamed".to_string(),
21268            };
21269            Ok(Some(lsp::WorkspaceEdit::new(
21270                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21271            )))
21272        });
21273    let rename_task = cx
21274        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21275        .expect("Confirm rename was not started");
21276    rename_handler.next().await.unwrap();
21277    rename_task.await.expect("Confirm rename failed");
21278    cx.run_until_parked();
21279
21280    // Correct range is renamed, as `surrounding_word` is used to find it.
21281    cx.assert_editor_state(indoc! {"
21282        struct FooRenamedˇ {}
21283    "});
21284}
21285
21286#[gpui::test]
21287async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21288    init_test(cx, |_| {});
21289    let mut cx = EditorTestContext::new(cx).await;
21290
21291    let language = Arc::new(
21292        Language::new(
21293            LanguageConfig::default(),
21294            Some(tree_sitter_html::LANGUAGE.into()),
21295        )
21296        .with_brackets_query(
21297            r#"
21298            ("<" @open "/>" @close)
21299            ("</" @open ">" @close)
21300            ("<" @open ">" @close)
21301            ("\"" @open "\"" @close)
21302            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21303        "#,
21304        )
21305        .unwrap(),
21306    );
21307    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21308
21309    cx.set_state(indoc! {"
21310        <span>ˇ</span>
21311    "});
21312    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21313    cx.assert_editor_state(indoc! {"
21314        <span>
21315        ˇ
21316        </span>
21317    "});
21318
21319    cx.set_state(indoc! {"
21320        <span><span></span>ˇ</span>
21321    "});
21322    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21323    cx.assert_editor_state(indoc! {"
21324        <span><span></span>
21325        ˇ</span>
21326    "});
21327
21328    cx.set_state(indoc! {"
21329        <span>ˇ
21330        </span>
21331    "});
21332    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21333    cx.assert_editor_state(indoc! {"
21334        <span>
21335        ˇ
21336        </span>
21337    "});
21338}
21339
21340#[gpui::test(iterations = 10)]
21341async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21342    init_test(cx, |_| {});
21343
21344    let fs = FakeFs::new(cx.executor());
21345    fs.insert_tree(
21346        path!("/dir"),
21347        json!({
21348            "a.ts": "a",
21349        }),
21350    )
21351    .await;
21352
21353    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21354    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21355    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21356
21357    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21358    language_registry.add(Arc::new(Language::new(
21359        LanguageConfig {
21360            name: "TypeScript".into(),
21361            matcher: LanguageMatcher {
21362                path_suffixes: vec!["ts".to_string()],
21363                ..Default::default()
21364            },
21365            ..Default::default()
21366        },
21367        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21368    )));
21369    let mut fake_language_servers = language_registry.register_fake_lsp(
21370        "TypeScript",
21371        FakeLspAdapter {
21372            capabilities: lsp::ServerCapabilities {
21373                code_lens_provider: Some(lsp::CodeLensOptions {
21374                    resolve_provider: Some(true),
21375                }),
21376                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21377                    commands: vec!["_the/command".to_string()],
21378                    ..lsp::ExecuteCommandOptions::default()
21379                }),
21380                ..lsp::ServerCapabilities::default()
21381            },
21382            ..FakeLspAdapter::default()
21383        },
21384    );
21385
21386    let (buffer, _handle) = project
21387        .update(cx, |p, cx| {
21388            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21389        })
21390        .await
21391        .unwrap();
21392    cx.executor().run_until_parked();
21393
21394    let fake_server = fake_language_servers.next().await.unwrap();
21395
21396    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21397    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21398    drop(buffer_snapshot);
21399    let actions = cx
21400        .update_window(*workspace, |_, window, cx| {
21401            project.code_actions(&buffer, anchor..anchor, window, cx)
21402        })
21403        .unwrap();
21404
21405    fake_server
21406        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21407            Ok(Some(vec![
21408                lsp::CodeLens {
21409                    range: lsp::Range::default(),
21410                    command: Some(lsp::Command {
21411                        title: "Code lens command".to_owned(),
21412                        command: "_the/command".to_owned(),
21413                        arguments: None,
21414                    }),
21415                    data: None,
21416                },
21417                lsp::CodeLens {
21418                    range: lsp::Range::default(),
21419                    command: Some(lsp::Command {
21420                        title: "Command not in capabilities".to_owned(),
21421                        command: "not in capabilities".to_owned(),
21422                        arguments: None,
21423                    }),
21424                    data: None,
21425                },
21426                lsp::CodeLens {
21427                    range: lsp::Range {
21428                        start: lsp::Position {
21429                            line: 1,
21430                            character: 1,
21431                        },
21432                        end: lsp::Position {
21433                            line: 1,
21434                            character: 1,
21435                        },
21436                    },
21437                    command: Some(lsp::Command {
21438                        title: "Command not in range".to_owned(),
21439                        command: "_the/command".to_owned(),
21440                        arguments: None,
21441                    }),
21442                    data: None,
21443                },
21444            ]))
21445        })
21446        .next()
21447        .await;
21448
21449    let actions = actions.await.unwrap();
21450    assert_eq!(
21451        actions.len(),
21452        1,
21453        "Should have only one valid action for the 0..0 range"
21454    );
21455    let action = actions[0].clone();
21456    let apply = project.update(cx, |project, cx| {
21457        project.apply_code_action(buffer.clone(), action, true, cx)
21458    });
21459
21460    // Resolving the code action does not populate its edits. In absence of
21461    // edits, we must execute the given command.
21462    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21463        |mut lens, _| async move {
21464            let lens_command = lens.command.as_mut().expect("should have a command");
21465            assert_eq!(lens_command.title, "Code lens command");
21466            lens_command.arguments = Some(vec![json!("the-argument")]);
21467            Ok(lens)
21468        },
21469    );
21470
21471    // While executing the command, the language server sends the editor
21472    // a `workspaceEdit` request.
21473    fake_server
21474        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21475            let fake = fake_server.clone();
21476            move |params, _| {
21477                assert_eq!(params.command, "_the/command");
21478                let fake = fake.clone();
21479                async move {
21480                    fake.server
21481                        .request::<lsp::request::ApplyWorkspaceEdit>(
21482                            lsp::ApplyWorkspaceEditParams {
21483                                label: None,
21484                                edit: lsp::WorkspaceEdit {
21485                                    changes: Some(
21486                                        [(
21487                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21488                                            vec![lsp::TextEdit {
21489                                                range: lsp::Range::new(
21490                                                    lsp::Position::new(0, 0),
21491                                                    lsp::Position::new(0, 0),
21492                                                ),
21493                                                new_text: "X".into(),
21494                                            }],
21495                                        )]
21496                                        .into_iter()
21497                                        .collect(),
21498                                    ),
21499                                    ..Default::default()
21500                                },
21501                            },
21502                        )
21503                        .await
21504                        .into_response()
21505                        .unwrap();
21506                    Ok(Some(json!(null)))
21507                }
21508            }
21509        })
21510        .next()
21511        .await;
21512
21513    // Applying the code lens command returns a project transaction containing the edits
21514    // sent by the language server in its `workspaceEdit` request.
21515    let transaction = apply.await.unwrap();
21516    assert!(transaction.0.contains_key(&buffer));
21517    buffer.update(cx, |buffer, cx| {
21518        assert_eq!(buffer.text(), "Xa");
21519        buffer.undo(cx);
21520        assert_eq!(buffer.text(), "a");
21521    });
21522}
21523
21524#[gpui::test]
21525async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21526    init_test(cx, |_| {});
21527
21528    let fs = FakeFs::new(cx.executor());
21529    let main_text = r#"fn main() {
21530println!("1");
21531println!("2");
21532println!("3");
21533println!("4");
21534println!("5");
21535}"#;
21536    let lib_text = "mod foo {}";
21537    fs.insert_tree(
21538        path!("/a"),
21539        json!({
21540            "lib.rs": lib_text,
21541            "main.rs": main_text,
21542        }),
21543    )
21544    .await;
21545
21546    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21547    let (workspace, cx) =
21548        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21549    let worktree_id = workspace.update(cx, |workspace, cx| {
21550        workspace.project().update(cx, |project, cx| {
21551            project.worktrees(cx).next().unwrap().read(cx).id()
21552        })
21553    });
21554
21555    let expected_ranges = vec![
21556        Point::new(0, 0)..Point::new(0, 0),
21557        Point::new(1, 0)..Point::new(1, 1),
21558        Point::new(2, 0)..Point::new(2, 2),
21559        Point::new(3, 0)..Point::new(3, 3),
21560    ];
21561
21562    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21563    let editor_1 = workspace
21564        .update_in(cx, |workspace, window, cx| {
21565            workspace.open_path(
21566                (worktree_id, "main.rs"),
21567                Some(pane_1.downgrade()),
21568                true,
21569                window,
21570                cx,
21571            )
21572        })
21573        .unwrap()
21574        .await
21575        .downcast::<Editor>()
21576        .unwrap();
21577    pane_1.update(cx, |pane, cx| {
21578        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21579        open_editor.update(cx, |editor, cx| {
21580            assert_eq!(
21581                editor.display_text(cx),
21582                main_text,
21583                "Original main.rs text on initial open",
21584            );
21585            assert_eq!(
21586                editor
21587                    .selections
21588                    .all::<Point>(cx)
21589                    .into_iter()
21590                    .map(|s| s.range())
21591                    .collect::<Vec<_>>(),
21592                vec![Point::zero()..Point::zero()],
21593                "Default selections on initial open",
21594            );
21595        })
21596    });
21597    editor_1.update_in(cx, |editor, window, cx| {
21598        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21599            s.select_ranges(expected_ranges.clone());
21600        });
21601    });
21602
21603    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21604        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21605    });
21606    let editor_2 = workspace
21607        .update_in(cx, |workspace, window, cx| {
21608            workspace.open_path(
21609                (worktree_id, "main.rs"),
21610                Some(pane_2.downgrade()),
21611                true,
21612                window,
21613                cx,
21614            )
21615        })
21616        .unwrap()
21617        .await
21618        .downcast::<Editor>()
21619        .unwrap();
21620    pane_2.update(cx, |pane, cx| {
21621        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21622        open_editor.update(cx, |editor, cx| {
21623            assert_eq!(
21624                editor.display_text(cx),
21625                main_text,
21626                "Original main.rs text on initial open in another panel",
21627            );
21628            assert_eq!(
21629                editor
21630                    .selections
21631                    .all::<Point>(cx)
21632                    .into_iter()
21633                    .map(|s| s.range())
21634                    .collect::<Vec<_>>(),
21635                vec![Point::zero()..Point::zero()],
21636                "Default selections on initial open in another panel",
21637            );
21638        })
21639    });
21640
21641    editor_2.update_in(cx, |editor, window, cx| {
21642        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21643    });
21644
21645    let _other_editor_1 = workspace
21646        .update_in(cx, |workspace, window, cx| {
21647            workspace.open_path(
21648                (worktree_id, "lib.rs"),
21649                Some(pane_1.downgrade()),
21650                true,
21651                window,
21652                cx,
21653            )
21654        })
21655        .unwrap()
21656        .await
21657        .downcast::<Editor>()
21658        .unwrap();
21659    pane_1
21660        .update_in(cx, |pane, window, cx| {
21661            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21662        })
21663        .await
21664        .unwrap();
21665    drop(editor_1);
21666    pane_1.update(cx, |pane, cx| {
21667        pane.active_item()
21668            .unwrap()
21669            .downcast::<Editor>()
21670            .unwrap()
21671            .update(cx, |editor, cx| {
21672                assert_eq!(
21673                    editor.display_text(cx),
21674                    lib_text,
21675                    "Other file should be open and active",
21676                );
21677            });
21678        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21679    });
21680
21681    let _other_editor_2 = workspace
21682        .update_in(cx, |workspace, window, cx| {
21683            workspace.open_path(
21684                (worktree_id, "lib.rs"),
21685                Some(pane_2.downgrade()),
21686                true,
21687                window,
21688                cx,
21689            )
21690        })
21691        .unwrap()
21692        .await
21693        .downcast::<Editor>()
21694        .unwrap();
21695    pane_2
21696        .update_in(cx, |pane, window, cx| {
21697            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21698        })
21699        .await
21700        .unwrap();
21701    drop(editor_2);
21702    pane_2.update(cx, |pane, cx| {
21703        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21704        open_editor.update(cx, |editor, cx| {
21705            assert_eq!(
21706                editor.display_text(cx),
21707                lib_text,
21708                "Other file should be open and active in another panel too",
21709            );
21710        });
21711        assert_eq!(
21712            pane.items().count(),
21713            1,
21714            "No other editors should be open in another pane",
21715        );
21716    });
21717
21718    let _editor_1_reopened = workspace
21719        .update_in(cx, |workspace, window, cx| {
21720            workspace.open_path(
21721                (worktree_id, "main.rs"),
21722                Some(pane_1.downgrade()),
21723                true,
21724                window,
21725                cx,
21726            )
21727        })
21728        .unwrap()
21729        .await
21730        .downcast::<Editor>()
21731        .unwrap();
21732    let _editor_2_reopened = workspace
21733        .update_in(cx, |workspace, window, cx| {
21734            workspace.open_path(
21735                (worktree_id, "main.rs"),
21736                Some(pane_2.downgrade()),
21737                true,
21738                window,
21739                cx,
21740            )
21741        })
21742        .unwrap()
21743        .await
21744        .downcast::<Editor>()
21745        .unwrap();
21746    pane_1.update(cx, |pane, cx| {
21747        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21748        open_editor.update(cx, |editor, cx| {
21749            assert_eq!(
21750                editor.display_text(cx),
21751                main_text,
21752                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21753            );
21754            assert_eq!(
21755                editor
21756                    .selections
21757                    .all::<Point>(cx)
21758                    .into_iter()
21759                    .map(|s| s.range())
21760                    .collect::<Vec<_>>(),
21761                expected_ranges,
21762                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21763            );
21764        })
21765    });
21766    pane_2.update(cx, |pane, cx| {
21767        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21768        open_editor.update(cx, |editor, cx| {
21769            assert_eq!(
21770                editor.display_text(cx),
21771                r#"fn main() {
21772⋯rintln!("1");
21773⋯intln!("2");
21774⋯ntln!("3");
21775println!("4");
21776println!("5");
21777}"#,
21778                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21779            );
21780            assert_eq!(
21781                editor
21782                    .selections
21783                    .all::<Point>(cx)
21784                    .into_iter()
21785                    .map(|s| s.range())
21786                    .collect::<Vec<_>>(),
21787                vec![Point::zero()..Point::zero()],
21788                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21789            );
21790        })
21791    });
21792}
21793
21794#[gpui::test]
21795async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21796    init_test(cx, |_| {});
21797
21798    let fs = FakeFs::new(cx.executor());
21799    let main_text = r#"fn main() {
21800println!("1");
21801println!("2");
21802println!("3");
21803println!("4");
21804println!("5");
21805}"#;
21806    let lib_text = "mod foo {}";
21807    fs.insert_tree(
21808        path!("/a"),
21809        json!({
21810            "lib.rs": lib_text,
21811            "main.rs": main_text,
21812        }),
21813    )
21814    .await;
21815
21816    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21817    let (workspace, cx) =
21818        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21819    let worktree_id = workspace.update(cx, |workspace, cx| {
21820        workspace.project().update(cx, |project, cx| {
21821            project.worktrees(cx).next().unwrap().read(cx).id()
21822        })
21823    });
21824
21825    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21826    let editor = workspace
21827        .update_in(cx, |workspace, window, cx| {
21828            workspace.open_path(
21829                (worktree_id, "main.rs"),
21830                Some(pane.downgrade()),
21831                true,
21832                window,
21833                cx,
21834            )
21835        })
21836        .unwrap()
21837        .await
21838        .downcast::<Editor>()
21839        .unwrap();
21840    pane.update(cx, |pane, cx| {
21841        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21842        open_editor.update(cx, |editor, cx| {
21843            assert_eq!(
21844                editor.display_text(cx),
21845                main_text,
21846                "Original main.rs text on initial open",
21847            );
21848        })
21849    });
21850    editor.update_in(cx, |editor, window, cx| {
21851        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21852    });
21853
21854    cx.update_global(|store: &mut SettingsStore, cx| {
21855        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21856            s.restore_on_file_reopen = Some(false);
21857        });
21858    });
21859    editor.update_in(cx, |editor, window, cx| {
21860        editor.fold_ranges(
21861            vec![
21862                Point::new(1, 0)..Point::new(1, 1),
21863                Point::new(2, 0)..Point::new(2, 2),
21864                Point::new(3, 0)..Point::new(3, 3),
21865            ],
21866            false,
21867            window,
21868            cx,
21869        );
21870    });
21871    pane.update_in(cx, |pane, window, cx| {
21872        pane.close_all_items(&CloseAllItems::default(), window, cx)
21873    })
21874    .await
21875    .unwrap();
21876    pane.update(cx, |pane, _| {
21877        assert!(pane.active_item().is_none());
21878    });
21879    cx.update_global(|store: &mut SettingsStore, cx| {
21880        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21881            s.restore_on_file_reopen = Some(true);
21882        });
21883    });
21884
21885    let _editor_reopened = workspace
21886        .update_in(cx, |workspace, window, cx| {
21887            workspace.open_path(
21888                (worktree_id, "main.rs"),
21889                Some(pane.downgrade()),
21890                true,
21891                window,
21892                cx,
21893            )
21894        })
21895        .unwrap()
21896        .await
21897        .downcast::<Editor>()
21898        .unwrap();
21899    pane.update(cx, |pane, cx| {
21900        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21901        open_editor.update(cx, |editor, cx| {
21902            assert_eq!(
21903                editor.display_text(cx),
21904                main_text,
21905                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21906            );
21907        })
21908    });
21909}
21910
21911#[gpui::test]
21912async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21913    struct EmptyModalView {
21914        focus_handle: gpui::FocusHandle,
21915    }
21916    impl EventEmitter<DismissEvent> for EmptyModalView {}
21917    impl Render for EmptyModalView {
21918        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21919            div()
21920        }
21921    }
21922    impl Focusable for EmptyModalView {
21923        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21924            self.focus_handle.clone()
21925        }
21926    }
21927    impl workspace::ModalView for EmptyModalView {}
21928    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21929        EmptyModalView {
21930            focus_handle: cx.focus_handle(),
21931        }
21932    }
21933
21934    init_test(cx, |_| {});
21935
21936    let fs = FakeFs::new(cx.executor());
21937    let project = Project::test(fs, [], cx).await;
21938    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21939    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21940    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21941    let editor = cx.new_window_entity(|window, cx| {
21942        Editor::new(
21943            EditorMode::full(),
21944            buffer,
21945            Some(project.clone()),
21946            window,
21947            cx,
21948        )
21949    });
21950    workspace
21951        .update(cx, |workspace, window, cx| {
21952            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21953        })
21954        .unwrap();
21955    editor.update_in(cx, |editor, window, cx| {
21956        editor.open_context_menu(&OpenContextMenu, window, cx);
21957        assert!(editor.mouse_context_menu.is_some());
21958    });
21959    workspace
21960        .update(cx, |workspace, window, cx| {
21961            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21962        })
21963        .unwrap();
21964    cx.read(|cx| {
21965        assert!(editor.read(cx).mouse_context_menu.is_none());
21966    });
21967}
21968
21969#[gpui::test]
21970async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21971    init_test(cx, |_| {});
21972
21973    let fs = FakeFs::new(cx.executor());
21974    fs.insert_file(path!("/file.html"), Default::default())
21975        .await;
21976
21977    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21978
21979    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21980    let html_language = Arc::new(Language::new(
21981        LanguageConfig {
21982            name: "HTML".into(),
21983            matcher: LanguageMatcher {
21984                path_suffixes: vec!["html".to_string()],
21985                ..LanguageMatcher::default()
21986            },
21987            brackets: BracketPairConfig {
21988                pairs: vec![BracketPair {
21989                    start: "<".into(),
21990                    end: ">".into(),
21991                    close: true,
21992                    ..Default::default()
21993                }],
21994                ..Default::default()
21995            },
21996            ..Default::default()
21997        },
21998        Some(tree_sitter_html::LANGUAGE.into()),
21999    ));
22000    language_registry.add(html_language);
22001    let mut fake_servers = language_registry.register_fake_lsp(
22002        "HTML",
22003        FakeLspAdapter {
22004            capabilities: lsp::ServerCapabilities {
22005                completion_provider: Some(lsp::CompletionOptions {
22006                    resolve_provider: Some(true),
22007                    ..Default::default()
22008                }),
22009                ..Default::default()
22010            },
22011            ..Default::default()
22012        },
22013    );
22014
22015    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22016    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22017
22018    let worktree_id = workspace
22019        .update(cx, |workspace, _window, cx| {
22020            workspace.project().update(cx, |project, cx| {
22021                project.worktrees(cx).next().unwrap().read(cx).id()
22022            })
22023        })
22024        .unwrap();
22025    project
22026        .update(cx, |project, cx| {
22027            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22028        })
22029        .await
22030        .unwrap();
22031    let editor = workspace
22032        .update(cx, |workspace, window, cx| {
22033            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22034        })
22035        .unwrap()
22036        .await
22037        .unwrap()
22038        .downcast::<Editor>()
22039        .unwrap();
22040
22041    let fake_server = fake_servers.next().await.unwrap();
22042    editor.update_in(cx, |editor, window, cx| {
22043        editor.set_text("<ad></ad>", window, cx);
22044        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22045            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22046        });
22047        let Some((buffer, _)) = editor
22048            .buffer
22049            .read(cx)
22050            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22051        else {
22052            panic!("Failed to get buffer for selection position");
22053        };
22054        let buffer = buffer.read(cx);
22055        let buffer_id = buffer.remote_id();
22056        let opening_range =
22057            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22058        let closing_range =
22059            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22060        let mut linked_ranges = HashMap::default();
22061        linked_ranges.insert(
22062            buffer_id,
22063            vec![(opening_range.clone(), vec![closing_range.clone()])],
22064        );
22065        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22066    });
22067    let mut completion_handle =
22068        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22069            Ok(Some(lsp::CompletionResponse::Array(vec![
22070                lsp::CompletionItem {
22071                    label: "head".to_string(),
22072                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22073                        lsp::InsertReplaceEdit {
22074                            new_text: "head".to_string(),
22075                            insert: lsp::Range::new(
22076                                lsp::Position::new(0, 1),
22077                                lsp::Position::new(0, 3),
22078                            ),
22079                            replace: lsp::Range::new(
22080                                lsp::Position::new(0, 1),
22081                                lsp::Position::new(0, 3),
22082                            ),
22083                        },
22084                    )),
22085                    ..Default::default()
22086                },
22087            ])))
22088        });
22089    editor.update_in(cx, |editor, window, cx| {
22090        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22091    });
22092    cx.run_until_parked();
22093    completion_handle.next().await.unwrap();
22094    editor.update(cx, |editor, _| {
22095        assert!(
22096            editor.context_menu_visible(),
22097            "Completion menu should be visible"
22098        );
22099    });
22100    editor.update_in(cx, |editor, window, cx| {
22101        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22102    });
22103    cx.executor().run_until_parked();
22104    editor.update(cx, |editor, cx| {
22105        assert_eq!(editor.text(cx), "<head></head>");
22106    });
22107}
22108
22109#[gpui::test]
22110async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22111    init_test(cx, |_| {});
22112
22113    let fs = FakeFs::new(cx.executor());
22114    fs.insert_tree(
22115        path!("/root"),
22116        json!({
22117            "a": {
22118                "main.rs": "fn main() {}",
22119            },
22120            "foo": {
22121                "bar": {
22122                    "external_file.rs": "pub mod external {}",
22123                }
22124            }
22125        }),
22126    )
22127    .await;
22128
22129    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22130    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22131    language_registry.add(rust_lang());
22132    let _fake_servers = language_registry.register_fake_lsp(
22133        "Rust",
22134        FakeLspAdapter {
22135            ..FakeLspAdapter::default()
22136        },
22137    );
22138    let (workspace, cx) =
22139        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22140    let worktree_id = workspace.update(cx, |workspace, cx| {
22141        workspace.project().update(cx, |project, cx| {
22142            project.worktrees(cx).next().unwrap().read(cx).id()
22143        })
22144    });
22145
22146    let assert_language_servers_count =
22147        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22148            project.update(cx, |project, cx| {
22149                let current = project
22150                    .lsp_store()
22151                    .read(cx)
22152                    .as_local()
22153                    .unwrap()
22154                    .language_servers
22155                    .len();
22156                assert_eq!(expected, current, "{context}");
22157            });
22158        };
22159
22160    assert_language_servers_count(
22161        0,
22162        "No servers should be running before any file is open",
22163        cx,
22164    );
22165    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22166    let main_editor = workspace
22167        .update_in(cx, |workspace, window, cx| {
22168            workspace.open_path(
22169                (worktree_id, "main.rs"),
22170                Some(pane.downgrade()),
22171                true,
22172                window,
22173                cx,
22174            )
22175        })
22176        .unwrap()
22177        .await
22178        .downcast::<Editor>()
22179        .unwrap();
22180    pane.update(cx, |pane, cx| {
22181        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22182        open_editor.update(cx, |editor, cx| {
22183            assert_eq!(
22184                editor.display_text(cx),
22185                "fn main() {}",
22186                "Original main.rs text on initial open",
22187            );
22188        });
22189        assert_eq!(open_editor, main_editor);
22190    });
22191    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22192
22193    let external_editor = workspace
22194        .update_in(cx, |workspace, window, cx| {
22195            workspace.open_abs_path(
22196                PathBuf::from("/root/foo/bar/external_file.rs"),
22197                OpenOptions::default(),
22198                window,
22199                cx,
22200            )
22201        })
22202        .await
22203        .expect("opening external file")
22204        .downcast::<Editor>()
22205        .expect("downcasted external file's open element to editor");
22206    pane.update(cx, |pane, cx| {
22207        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22208        open_editor.update(cx, |editor, cx| {
22209            assert_eq!(
22210                editor.display_text(cx),
22211                "pub mod external {}",
22212                "External file is open now",
22213            );
22214        });
22215        assert_eq!(open_editor, external_editor);
22216    });
22217    assert_language_servers_count(
22218        1,
22219        "Second, external, *.rs file should join the existing server",
22220        cx,
22221    );
22222
22223    pane.update_in(cx, |pane, window, cx| {
22224        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22225    })
22226    .await
22227    .unwrap();
22228    pane.update_in(cx, |pane, window, cx| {
22229        pane.navigate_backward(window, cx);
22230    });
22231    cx.run_until_parked();
22232    pane.update(cx, |pane, cx| {
22233        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22234        open_editor.update(cx, |editor, cx| {
22235            assert_eq!(
22236                editor.display_text(cx),
22237                "pub mod external {}",
22238                "External file is open now",
22239            );
22240        });
22241    });
22242    assert_language_servers_count(
22243        1,
22244        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22245        cx,
22246    );
22247
22248    cx.update(|_, cx| {
22249        workspace::reload(&workspace::Reload::default(), cx);
22250    });
22251    assert_language_servers_count(
22252        1,
22253        "After reloading the worktree with local and external files opened, only one project should be started",
22254        cx,
22255    );
22256}
22257
22258#[gpui::test]
22259async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22260    init_test(cx, |_| {});
22261
22262    let mut cx = EditorTestContext::new(cx).await;
22263    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22264    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22265
22266    // test cursor move to start of each line on tab
22267    // for `if`, `elif`, `else`, `while`, `with` and `for`
22268    cx.set_state(indoc! {"
22269        def main():
22270        ˇ    for item in items:
22271        ˇ        while item.active:
22272        ˇ            if item.value > 10:
22273        ˇ                continue
22274        ˇ            elif item.value < 0:
22275        ˇ                break
22276        ˇ            else:
22277        ˇ                with item.context() as ctx:
22278        ˇ                    yield count
22279        ˇ        else:
22280        ˇ            log('while else')
22281        ˇ    else:
22282        ˇ        log('for else')
22283    "});
22284    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22285    cx.assert_editor_state(indoc! {"
22286        def main():
22287            ˇfor item in items:
22288                ˇwhile item.active:
22289                    ˇif item.value > 10:
22290                        ˇcontinue
22291                    ˇelif item.value < 0:
22292                        ˇbreak
22293                    ˇelse:
22294                        ˇwith item.context() as ctx:
22295                            ˇyield count
22296                ˇelse:
22297                    ˇlog('while else')
22298            ˇelse:
22299                ˇlog('for else')
22300    "});
22301    // test relative indent is preserved when tab
22302    // for `if`, `elif`, `else`, `while`, `with` and `for`
22303    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22304    cx.assert_editor_state(indoc! {"
22305        def main():
22306                ˇfor item in items:
22307                    ˇwhile item.active:
22308                        ˇif item.value > 10:
22309                            ˇcontinue
22310                        ˇelif item.value < 0:
22311                            ˇbreak
22312                        ˇelse:
22313                            ˇwith item.context() as ctx:
22314                                ˇyield count
22315                    ˇelse:
22316                        ˇlog('while else')
22317                ˇelse:
22318                    ˇlog('for else')
22319    "});
22320
22321    // test cursor move to start of each line on tab
22322    // for `try`, `except`, `else`, `finally`, `match` and `def`
22323    cx.set_state(indoc! {"
22324        def main():
22325        ˇ    try:
22326        ˇ        fetch()
22327        ˇ    except ValueError:
22328        ˇ        handle_error()
22329        ˇ    else:
22330        ˇ        match value:
22331        ˇ            case _:
22332        ˇ    finally:
22333        ˇ        def status():
22334        ˇ            return 0
22335    "});
22336    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22337    cx.assert_editor_state(indoc! {"
22338        def main():
22339            ˇtry:
22340                ˇfetch()
22341            ˇexcept ValueError:
22342                ˇhandle_error()
22343            ˇelse:
22344                ˇmatch value:
22345                    ˇcase _:
22346            ˇfinally:
22347                ˇdef status():
22348                    ˇreturn 0
22349    "});
22350    // test relative indent is preserved when tab
22351    // for `try`, `except`, `else`, `finally`, `match` and `def`
22352    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22353    cx.assert_editor_state(indoc! {"
22354        def main():
22355                ˇtry:
22356                    ˇfetch()
22357                ˇexcept ValueError:
22358                    ˇhandle_error()
22359                ˇelse:
22360                    ˇmatch value:
22361                        ˇcase _:
22362                ˇfinally:
22363                    ˇdef status():
22364                        ˇreturn 0
22365    "});
22366}
22367
22368#[gpui::test]
22369async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22370    init_test(cx, |_| {});
22371
22372    let mut cx = EditorTestContext::new(cx).await;
22373    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22374    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22375
22376    // test `else` auto outdents when typed inside `if` block
22377    cx.set_state(indoc! {"
22378        def main():
22379            if i == 2:
22380                return
22381                ˇ
22382    "});
22383    cx.update_editor(|editor, window, cx| {
22384        editor.handle_input("else:", window, cx);
22385    });
22386    cx.assert_editor_state(indoc! {"
22387        def main():
22388            if i == 2:
22389                return
22390            else:ˇ
22391    "});
22392
22393    // test `except` auto outdents when typed inside `try` block
22394    cx.set_state(indoc! {"
22395        def main():
22396            try:
22397                i = 2
22398                ˇ
22399    "});
22400    cx.update_editor(|editor, window, cx| {
22401        editor.handle_input("except:", window, cx);
22402    });
22403    cx.assert_editor_state(indoc! {"
22404        def main():
22405            try:
22406                i = 2
22407            except:ˇ
22408    "});
22409
22410    // test `else` auto outdents when typed inside `except` block
22411    cx.set_state(indoc! {"
22412        def main():
22413            try:
22414                i = 2
22415            except:
22416                j = 2
22417                ˇ
22418    "});
22419    cx.update_editor(|editor, window, cx| {
22420        editor.handle_input("else:", window, cx);
22421    });
22422    cx.assert_editor_state(indoc! {"
22423        def main():
22424            try:
22425                i = 2
22426            except:
22427                j = 2
22428            else:ˇ
22429    "});
22430
22431    // test `finally` auto outdents when typed inside `else` block
22432    cx.set_state(indoc! {"
22433        def main():
22434            try:
22435                i = 2
22436            except:
22437                j = 2
22438            else:
22439                k = 2
22440                ˇ
22441    "});
22442    cx.update_editor(|editor, window, cx| {
22443        editor.handle_input("finally:", window, cx);
22444    });
22445    cx.assert_editor_state(indoc! {"
22446        def main():
22447            try:
22448                i = 2
22449            except:
22450                j = 2
22451            else:
22452                k = 2
22453            finally:ˇ
22454    "});
22455
22456    // test `else` does not outdents when typed inside `except` block right after for block
22457    cx.set_state(indoc! {"
22458        def main():
22459            try:
22460                i = 2
22461            except:
22462                for i in range(n):
22463                    pass
22464                ˇ
22465    "});
22466    cx.update_editor(|editor, window, cx| {
22467        editor.handle_input("else:", window, cx);
22468    });
22469    cx.assert_editor_state(indoc! {"
22470        def main():
22471            try:
22472                i = 2
22473            except:
22474                for i in range(n):
22475                    pass
22476                else:ˇ
22477    "});
22478
22479    // test `finally` auto outdents when typed inside `else` block right after for block
22480    cx.set_state(indoc! {"
22481        def main():
22482            try:
22483                i = 2
22484            except:
22485                j = 2
22486            else:
22487                for i in range(n):
22488                    pass
22489                ˇ
22490    "});
22491    cx.update_editor(|editor, window, cx| {
22492        editor.handle_input("finally:", window, cx);
22493    });
22494    cx.assert_editor_state(indoc! {"
22495        def main():
22496            try:
22497                i = 2
22498            except:
22499                j = 2
22500            else:
22501                for i in range(n):
22502                    pass
22503            finally:ˇ
22504    "});
22505
22506    // test `except` outdents to inner "try" block
22507    cx.set_state(indoc! {"
22508        def main():
22509            try:
22510                i = 2
22511                if i == 2:
22512                    try:
22513                        i = 3
22514                        ˇ
22515    "});
22516    cx.update_editor(|editor, window, cx| {
22517        editor.handle_input("except:", window, cx);
22518    });
22519    cx.assert_editor_state(indoc! {"
22520        def main():
22521            try:
22522                i = 2
22523                if i == 2:
22524                    try:
22525                        i = 3
22526                    except:ˇ
22527    "});
22528
22529    // test `except` outdents to outer "try" block
22530    cx.set_state(indoc! {"
22531        def main():
22532            try:
22533                i = 2
22534                if i == 2:
22535                    try:
22536                        i = 3
22537                ˇ
22538    "});
22539    cx.update_editor(|editor, window, cx| {
22540        editor.handle_input("except:", window, cx);
22541    });
22542    cx.assert_editor_state(indoc! {"
22543        def main():
22544            try:
22545                i = 2
22546                if i == 2:
22547                    try:
22548                        i = 3
22549            except:ˇ
22550    "});
22551
22552    // test `else` stays at correct indent when typed after `for` block
22553    cx.set_state(indoc! {"
22554        def main():
22555            for i in range(10):
22556                if i == 3:
22557                    break
22558            ˇ
22559    "});
22560    cx.update_editor(|editor, window, cx| {
22561        editor.handle_input("else:", window, cx);
22562    });
22563    cx.assert_editor_state(indoc! {"
22564        def main():
22565            for i in range(10):
22566                if i == 3:
22567                    break
22568            else:ˇ
22569    "});
22570
22571    // test does not outdent on typing after line with square brackets
22572    cx.set_state(indoc! {"
22573        def f() -> list[str]:
22574            ˇ
22575    "});
22576    cx.update_editor(|editor, window, cx| {
22577        editor.handle_input("a", window, cx);
22578    });
22579    cx.assert_editor_state(indoc! {"
22580        def f() -> list[str]:
2258122582    "});
22583
22584    // test does not outdent on typing : after case keyword
22585    cx.set_state(indoc! {"
22586        match 1:
22587            caseˇ
22588    "});
22589    cx.update_editor(|editor, window, cx| {
22590        editor.handle_input(":", window, cx);
22591    });
22592    cx.assert_editor_state(indoc! {"
22593        match 1:
22594            case:ˇ
22595    "});
22596}
22597
22598#[gpui::test]
22599async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22600    init_test(cx, |_| {});
22601    update_test_language_settings(cx, |settings| {
22602        settings.defaults.extend_comment_on_newline = Some(false);
22603    });
22604    let mut cx = EditorTestContext::new(cx).await;
22605    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22606    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22607
22608    // test correct indent after newline on comment
22609    cx.set_state(indoc! {"
22610        # COMMENT:ˇ
22611    "});
22612    cx.update_editor(|editor, window, cx| {
22613        editor.newline(&Newline, window, cx);
22614    });
22615    cx.assert_editor_state(indoc! {"
22616        # COMMENT:
22617        ˇ
22618    "});
22619
22620    // test correct indent after newline in brackets
22621    cx.set_state(indoc! {"
22622        {ˇ}
22623    "});
22624    cx.update_editor(|editor, window, cx| {
22625        editor.newline(&Newline, window, cx);
22626    });
22627    cx.run_until_parked();
22628    cx.assert_editor_state(indoc! {"
22629        {
22630            ˇ
22631        }
22632    "});
22633
22634    cx.set_state(indoc! {"
22635        (ˇ)
22636    "});
22637    cx.update_editor(|editor, window, cx| {
22638        editor.newline(&Newline, window, cx);
22639    });
22640    cx.run_until_parked();
22641    cx.assert_editor_state(indoc! {"
22642        (
22643            ˇ
22644        )
22645    "});
22646
22647    // do not indent after empty lists or dictionaries
22648    cx.set_state(indoc! {"
22649        a = []ˇ
22650    "});
22651    cx.update_editor(|editor, window, cx| {
22652        editor.newline(&Newline, window, cx);
22653    });
22654    cx.run_until_parked();
22655    cx.assert_editor_state(indoc! {"
22656        a = []
22657        ˇ
22658    "});
22659}
22660
22661fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22662    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22663    point..point
22664}
22665
22666#[track_caller]
22667fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22668    let (text, ranges) = marked_text_ranges(marked_text, true);
22669    assert_eq!(editor.text(cx), text);
22670    assert_eq!(
22671        editor.selections.ranges(cx),
22672        ranges,
22673        "Assert selections are {}",
22674        marked_text
22675    );
22676}
22677
22678pub fn handle_signature_help_request(
22679    cx: &mut EditorLspTestContext,
22680    mocked_response: lsp::SignatureHelp,
22681) -> impl Future<Output = ()> + use<> {
22682    let mut request =
22683        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22684            let mocked_response = mocked_response.clone();
22685            async move { Ok(Some(mocked_response)) }
22686        });
22687
22688    async move {
22689        request.next().await;
22690    }
22691}
22692
22693#[track_caller]
22694pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22695    cx.update_editor(|editor, _, _| {
22696        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22697            let entries = menu.entries.borrow();
22698            let entries = entries
22699                .iter()
22700                .map(|entry| entry.string.as_str())
22701                .collect::<Vec<_>>();
22702            assert_eq!(entries, expected);
22703        } else {
22704            panic!("Expected completions menu");
22705        }
22706    });
22707}
22708
22709/// Handle completion request passing a marked string specifying where the completion
22710/// should be triggered from using '|' character, what range should be replaced, and what completions
22711/// should be returned using '<' and '>' to delimit the range.
22712///
22713/// Also see `handle_completion_request_with_insert_and_replace`.
22714#[track_caller]
22715pub fn handle_completion_request(
22716    marked_string: &str,
22717    completions: Vec<&'static str>,
22718    is_incomplete: bool,
22719    counter: Arc<AtomicUsize>,
22720    cx: &mut EditorLspTestContext,
22721) -> impl Future<Output = ()> {
22722    let complete_from_marker: TextRangeMarker = '|'.into();
22723    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22724    let (_, mut marked_ranges) = marked_text_ranges_by(
22725        marked_string,
22726        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22727    );
22728
22729    let complete_from_position =
22730        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22731    let replace_range =
22732        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22733
22734    let mut request =
22735        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22736            let completions = completions.clone();
22737            counter.fetch_add(1, atomic::Ordering::Release);
22738            async move {
22739                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22740                assert_eq!(
22741                    params.text_document_position.position,
22742                    complete_from_position
22743                );
22744                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22745                    is_incomplete: is_incomplete,
22746                    item_defaults: None,
22747                    items: completions
22748                        .iter()
22749                        .map(|completion_text| lsp::CompletionItem {
22750                            label: completion_text.to_string(),
22751                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22752                                range: replace_range,
22753                                new_text: completion_text.to_string(),
22754                            })),
22755                            ..Default::default()
22756                        })
22757                        .collect(),
22758                })))
22759            }
22760        });
22761
22762    async move {
22763        request.next().await;
22764    }
22765}
22766
22767/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22768/// given instead, which also contains an `insert` range.
22769///
22770/// This function uses markers to define ranges:
22771/// - `|` marks the cursor position
22772/// - `<>` marks the replace range
22773/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22774pub fn handle_completion_request_with_insert_and_replace(
22775    cx: &mut EditorLspTestContext,
22776    marked_string: &str,
22777    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22778    counter: Arc<AtomicUsize>,
22779) -> impl Future<Output = ()> {
22780    let complete_from_marker: TextRangeMarker = '|'.into();
22781    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22782    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22783
22784    let (_, mut marked_ranges) = marked_text_ranges_by(
22785        marked_string,
22786        vec![
22787            complete_from_marker.clone(),
22788            replace_range_marker.clone(),
22789            insert_range_marker.clone(),
22790        ],
22791    );
22792
22793    let complete_from_position =
22794        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22795    let replace_range =
22796        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22797
22798    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22799        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22800        _ => lsp::Range {
22801            start: replace_range.start,
22802            end: complete_from_position,
22803        },
22804    };
22805
22806    let mut request =
22807        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22808            let completions = completions.clone();
22809            counter.fetch_add(1, atomic::Ordering::Release);
22810            async move {
22811                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22812                assert_eq!(
22813                    params.text_document_position.position, complete_from_position,
22814                    "marker `|` position doesn't match",
22815                );
22816                Ok(Some(lsp::CompletionResponse::Array(
22817                    completions
22818                        .iter()
22819                        .map(|(label, new_text)| lsp::CompletionItem {
22820                            label: label.to_string(),
22821                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22822                                lsp::InsertReplaceEdit {
22823                                    insert: insert_range,
22824                                    replace: replace_range,
22825                                    new_text: new_text.to_string(),
22826                                },
22827                            )),
22828                            ..Default::default()
22829                        })
22830                        .collect(),
22831                )))
22832            }
22833        });
22834
22835    async move {
22836        request.next().await;
22837    }
22838}
22839
22840fn handle_resolve_completion_request(
22841    cx: &mut EditorLspTestContext,
22842    edits: Option<Vec<(&'static str, &'static str)>>,
22843) -> impl Future<Output = ()> {
22844    let edits = edits.map(|edits| {
22845        edits
22846            .iter()
22847            .map(|(marked_string, new_text)| {
22848                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22849                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22850                lsp::TextEdit::new(replace_range, new_text.to_string())
22851            })
22852            .collect::<Vec<_>>()
22853    });
22854
22855    let mut request =
22856        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22857            let edits = edits.clone();
22858            async move {
22859                Ok(lsp::CompletionItem {
22860                    additional_text_edits: edits,
22861                    ..Default::default()
22862                })
22863            }
22864        });
22865
22866    async move {
22867        request.next().await;
22868    }
22869}
22870
22871pub(crate) fn update_test_language_settings(
22872    cx: &mut TestAppContext,
22873    f: impl Fn(&mut AllLanguageSettingsContent),
22874) {
22875    cx.update(|cx| {
22876        SettingsStore::update_global(cx, |store, cx| {
22877            store.update_user_settings::<AllLanguageSettings>(cx, f);
22878        });
22879    });
22880}
22881
22882pub(crate) fn update_test_project_settings(
22883    cx: &mut TestAppContext,
22884    f: impl Fn(&mut ProjectSettings),
22885) {
22886    cx.update(|cx| {
22887        SettingsStore::update_global(cx, |store, cx| {
22888            store.update_user_settings::<ProjectSettings>(cx, f);
22889        });
22890    });
22891}
22892
22893pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22894    cx.update(|cx| {
22895        assets::Assets.load_test_fonts(cx);
22896        let store = SettingsStore::test(cx);
22897        cx.set_global(store);
22898        theme::init(theme::LoadThemes::JustBase, cx);
22899        release_channel::init(SemanticVersion::default(), cx);
22900        client::init_settings(cx);
22901        language::init(cx);
22902        Project::init_settings(cx);
22903        workspace::init_settings(cx);
22904        crate::init(cx);
22905    });
22906    zlog::init_test();
22907    update_test_language_settings(cx, f);
22908}
22909
22910#[track_caller]
22911fn assert_hunk_revert(
22912    not_reverted_text_with_selections: &str,
22913    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22914    expected_reverted_text_with_selections: &str,
22915    base_text: &str,
22916    cx: &mut EditorLspTestContext,
22917) {
22918    cx.set_state(not_reverted_text_with_selections);
22919    cx.set_head_text(base_text);
22920    cx.executor().run_until_parked();
22921
22922    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22923        let snapshot = editor.snapshot(window, cx);
22924        let reverted_hunk_statuses = snapshot
22925            .buffer_snapshot
22926            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22927            .map(|hunk| hunk.status().kind)
22928            .collect::<Vec<_>>();
22929
22930        editor.git_restore(&Default::default(), window, cx);
22931        reverted_hunk_statuses
22932    });
22933    cx.executor().run_until_parked();
22934    cx.assert_editor_state(expected_reverted_text_with_selections);
22935    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22936}
22937
22938#[gpui::test(iterations = 10)]
22939async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22940    init_test(cx, |_| {});
22941
22942    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22943    let counter = diagnostic_requests.clone();
22944
22945    let fs = FakeFs::new(cx.executor());
22946    fs.insert_tree(
22947        path!("/a"),
22948        json!({
22949            "first.rs": "fn main() { let a = 5; }",
22950            "second.rs": "// Test file",
22951        }),
22952    )
22953    .await;
22954
22955    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22956    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22957    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22958
22959    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22960    language_registry.add(rust_lang());
22961    let mut fake_servers = language_registry.register_fake_lsp(
22962        "Rust",
22963        FakeLspAdapter {
22964            capabilities: lsp::ServerCapabilities {
22965                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22966                    lsp::DiagnosticOptions {
22967                        identifier: None,
22968                        inter_file_dependencies: true,
22969                        workspace_diagnostics: true,
22970                        work_done_progress_options: Default::default(),
22971                    },
22972                )),
22973                ..Default::default()
22974            },
22975            ..Default::default()
22976        },
22977    );
22978
22979    let editor = workspace
22980        .update(cx, |workspace, window, cx| {
22981            workspace.open_abs_path(
22982                PathBuf::from(path!("/a/first.rs")),
22983                OpenOptions::default(),
22984                window,
22985                cx,
22986            )
22987        })
22988        .unwrap()
22989        .await
22990        .unwrap()
22991        .downcast::<Editor>()
22992        .unwrap();
22993    let fake_server = fake_servers.next().await.unwrap();
22994    let server_id = fake_server.server.server_id();
22995    let mut first_request = fake_server
22996        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22997            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22998            let result_id = Some(new_result_id.to_string());
22999            assert_eq!(
23000                params.text_document.uri,
23001                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23002            );
23003            async move {
23004                Ok(lsp::DocumentDiagnosticReportResult::Report(
23005                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23006                        related_documents: None,
23007                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23008                            items: Vec::new(),
23009                            result_id,
23010                        },
23011                    }),
23012                ))
23013            }
23014        });
23015
23016    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23017        project.update(cx, |project, cx| {
23018            let buffer_id = editor
23019                .read(cx)
23020                .buffer()
23021                .read(cx)
23022                .as_singleton()
23023                .expect("created a singleton buffer")
23024                .read(cx)
23025                .remote_id();
23026            let buffer_result_id = project
23027                .lsp_store()
23028                .read(cx)
23029                .result_id(server_id, buffer_id, cx);
23030            assert_eq!(expected, buffer_result_id);
23031        });
23032    };
23033
23034    ensure_result_id(None, cx);
23035    cx.executor().advance_clock(Duration::from_millis(60));
23036    cx.executor().run_until_parked();
23037    assert_eq!(
23038        diagnostic_requests.load(atomic::Ordering::Acquire),
23039        1,
23040        "Opening file should trigger diagnostic request"
23041    );
23042    first_request
23043        .next()
23044        .await
23045        .expect("should have sent the first diagnostics pull request");
23046    ensure_result_id(Some("1".to_string()), cx);
23047
23048    // Editing should trigger diagnostics
23049    editor.update_in(cx, |editor, window, cx| {
23050        editor.handle_input("2", window, cx)
23051    });
23052    cx.executor().advance_clock(Duration::from_millis(60));
23053    cx.executor().run_until_parked();
23054    assert_eq!(
23055        diagnostic_requests.load(atomic::Ordering::Acquire),
23056        2,
23057        "Editing should trigger diagnostic request"
23058    );
23059    ensure_result_id(Some("2".to_string()), cx);
23060
23061    // Moving cursor should not trigger diagnostic request
23062    editor.update_in(cx, |editor, window, cx| {
23063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23064            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23065        });
23066    });
23067    cx.executor().advance_clock(Duration::from_millis(60));
23068    cx.executor().run_until_parked();
23069    assert_eq!(
23070        diagnostic_requests.load(atomic::Ordering::Acquire),
23071        2,
23072        "Cursor movement should not trigger diagnostic request"
23073    );
23074    ensure_result_id(Some("2".to_string()), cx);
23075    // Multiple rapid edits should be debounced
23076    for _ in 0..5 {
23077        editor.update_in(cx, |editor, window, cx| {
23078            editor.handle_input("x", window, cx)
23079        });
23080    }
23081    cx.executor().advance_clock(Duration::from_millis(60));
23082    cx.executor().run_until_parked();
23083
23084    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23085    assert!(
23086        final_requests <= 4,
23087        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23088    );
23089    ensure_result_id(Some(final_requests.to_string()), cx);
23090}
23091
23092#[gpui::test]
23093async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23094    // Regression test for issue #11671
23095    // Previously, adding a cursor after moving multiple cursors would reset
23096    // the cursor count instead of adding to the existing cursors.
23097    init_test(cx, |_| {});
23098    let mut cx = EditorTestContext::new(cx).await;
23099
23100    // Create a simple buffer with cursor at start
23101    cx.set_state(indoc! {"
23102        ˇaaaa
23103        bbbb
23104        cccc
23105        dddd
23106        eeee
23107        ffff
23108        gggg
23109        hhhh"});
23110
23111    // Add 2 cursors below (so we have 3 total)
23112    cx.update_editor(|editor, window, cx| {
23113        editor.add_selection_below(&Default::default(), window, cx);
23114        editor.add_selection_below(&Default::default(), window, cx);
23115    });
23116
23117    // Verify we have 3 cursors
23118    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23119    assert_eq!(
23120        initial_count, 3,
23121        "Should have 3 cursors after adding 2 below"
23122    );
23123
23124    // Move down one line
23125    cx.update_editor(|editor, window, cx| {
23126        editor.move_down(&MoveDown, window, cx);
23127    });
23128
23129    // Add another cursor below
23130    cx.update_editor(|editor, window, cx| {
23131        editor.add_selection_below(&Default::default(), window, cx);
23132    });
23133
23134    // Should now have 4 cursors (3 original + 1 new)
23135    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23136    assert_eq!(
23137        final_count, 4,
23138        "Should have 4 cursors after moving and adding another"
23139    );
23140}
23141
23142#[gpui::test(iterations = 10)]
23143async fn test_document_colors(cx: &mut TestAppContext) {
23144    let expected_color = Rgba {
23145        r: 0.33,
23146        g: 0.33,
23147        b: 0.33,
23148        a: 0.33,
23149    };
23150
23151    init_test(cx, |_| {});
23152
23153    let fs = FakeFs::new(cx.executor());
23154    fs.insert_tree(
23155        path!("/a"),
23156        json!({
23157            "first.rs": "fn main() { let a = 5; }",
23158        }),
23159    )
23160    .await;
23161
23162    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23163    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23164    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23165
23166    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23167    language_registry.add(rust_lang());
23168    let mut fake_servers = language_registry.register_fake_lsp(
23169        "Rust",
23170        FakeLspAdapter {
23171            capabilities: lsp::ServerCapabilities {
23172                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23173                ..lsp::ServerCapabilities::default()
23174            },
23175            name: "rust-analyzer",
23176            ..FakeLspAdapter::default()
23177        },
23178    );
23179    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23180        "Rust",
23181        FakeLspAdapter {
23182            capabilities: lsp::ServerCapabilities {
23183                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23184                ..lsp::ServerCapabilities::default()
23185            },
23186            name: "not-rust-analyzer",
23187            ..FakeLspAdapter::default()
23188        },
23189    );
23190
23191    let editor = workspace
23192        .update(cx, |workspace, window, cx| {
23193            workspace.open_abs_path(
23194                PathBuf::from(path!("/a/first.rs")),
23195                OpenOptions::default(),
23196                window,
23197                cx,
23198            )
23199        })
23200        .unwrap()
23201        .await
23202        .unwrap()
23203        .downcast::<Editor>()
23204        .unwrap();
23205    let fake_language_server = fake_servers.next().await.unwrap();
23206    let fake_language_server_without_capabilities =
23207        fake_servers_without_capabilities.next().await.unwrap();
23208    let requests_made = Arc::new(AtomicUsize::new(0));
23209    let closure_requests_made = Arc::clone(&requests_made);
23210    let mut color_request_handle = fake_language_server
23211        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23212            let requests_made = Arc::clone(&closure_requests_made);
23213            async move {
23214                assert_eq!(
23215                    params.text_document.uri,
23216                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23217                );
23218                requests_made.fetch_add(1, atomic::Ordering::Release);
23219                Ok(vec![
23220                    lsp::ColorInformation {
23221                        range: lsp::Range {
23222                            start: lsp::Position {
23223                                line: 0,
23224                                character: 0,
23225                            },
23226                            end: lsp::Position {
23227                                line: 0,
23228                                character: 1,
23229                            },
23230                        },
23231                        color: lsp::Color {
23232                            red: 0.33,
23233                            green: 0.33,
23234                            blue: 0.33,
23235                            alpha: 0.33,
23236                        },
23237                    },
23238                    lsp::ColorInformation {
23239                        range: lsp::Range {
23240                            start: lsp::Position {
23241                                line: 0,
23242                                character: 0,
23243                            },
23244                            end: lsp::Position {
23245                                line: 0,
23246                                character: 1,
23247                            },
23248                        },
23249                        color: lsp::Color {
23250                            red: 0.33,
23251                            green: 0.33,
23252                            blue: 0.33,
23253                            alpha: 0.33,
23254                        },
23255                    },
23256                ])
23257            }
23258        });
23259
23260    let _handle = fake_language_server_without_capabilities
23261        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23262            panic!("Should not be called");
23263        });
23264    cx.executor().advance_clock(Duration::from_millis(100));
23265    color_request_handle.next().await.unwrap();
23266    cx.run_until_parked();
23267    assert_eq!(
23268        1,
23269        requests_made.load(atomic::Ordering::Acquire),
23270        "Should query for colors once per editor open"
23271    );
23272    editor.update_in(cx, |editor, _, cx| {
23273        assert_eq!(
23274            vec![expected_color],
23275            extract_color_inlays(editor, cx),
23276            "Should have an initial inlay"
23277        );
23278    });
23279
23280    // opening another file in a split should not influence the LSP query counter
23281    workspace
23282        .update(cx, |workspace, window, cx| {
23283            assert_eq!(
23284                workspace.panes().len(),
23285                1,
23286                "Should have one pane with one editor"
23287            );
23288            workspace.move_item_to_pane_in_direction(
23289                &MoveItemToPaneInDirection {
23290                    direction: SplitDirection::Right,
23291                    focus: false,
23292                    clone: true,
23293                },
23294                window,
23295                cx,
23296            );
23297        })
23298        .unwrap();
23299    cx.run_until_parked();
23300    workspace
23301        .update(cx, |workspace, _, cx| {
23302            let panes = workspace.panes();
23303            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23304            for pane in panes {
23305                let editor = pane
23306                    .read(cx)
23307                    .active_item()
23308                    .and_then(|item| item.downcast::<Editor>())
23309                    .expect("Should have opened an editor in each split");
23310                let editor_file = editor
23311                    .read(cx)
23312                    .buffer()
23313                    .read(cx)
23314                    .as_singleton()
23315                    .expect("test deals with singleton buffers")
23316                    .read(cx)
23317                    .file()
23318                    .expect("test buffese should have a file")
23319                    .path();
23320                assert_eq!(
23321                    editor_file.as_ref(),
23322                    Path::new("first.rs"),
23323                    "Both editors should be opened for the same file"
23324                )
23325            }
23326        })
23327        .unwrap();
23328
23329    cx.executor().advance_clock(Duration::from_millis(500));
23330    let save = editor.update_in(cx, |editor, window, cx| {
23331        editor.move_to_end(&MoveToEnd, window, cx);
23332        editor.handle_input("dirty", window, cx);
23333        editor.save(
23334            SaveOptions {
23335                format: true,
23336                autosave: true,
23337            },
23338            project.clone(),
23339            window,
23340            cx,
23341        )
23342    });
23343    save.await.unwrap();
23344
23345    color_request_handle.next().await.unwrap();
23346    cx.run_until_parked();
23347    assert_eq!(
23348        3,
23349        requests_made.load(atomic::Ordering::Acquire),
23350        "Should query for colors once per save and once per formatting after save"
23351    );
23352
23353    drop(editor);
23354    let close = workspace
23355        .update(cx, |workspace, window, cx| {
23356            workspace.active_pane().update(cx, |pane, cx| {
23357                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23358            })
23359        })
23360        .unwrap();
23361    close.await.unwrap();
23362    let close = workspace
23363        .update(cx, |workspace, window, cx| {
23364            workspace.active_pane().update(cx, |pane, cx| {
23365                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23366            })
23367        })
23368        .unwrap();
23369    close.await.unwrap();
23370    assert_eq!(
23371        3,
23372        requests_made.load(atomic::Ordering::Acquire),
23373        "After saving and closing all editors, no extra requests should be made"
23374    );
23375    workspace
23376        .update(cx, |workspace, _, cx| {
23377            assert!(
23378                workspace.active_item(cx).is_none(),
23379                "Should close all editors"
23380            )
23381        })
23382        .unwrap();
23383
23384    workspace
23385        .update(cx, |workspace, window, cx| {
23386            workspace.active_pane().update(cx, |pane, cx| {
23387                pane.navigate_backward(window, cx);
23388            })
23389        })
23390        .unwrap();
23391    cx.executor().advance_clock(Duration::from_millis(100));
23392    cx.run_until_parked();
23393    let editor = workspace
23394        .update(cx, |workspace, _, cx| {
23395            workspace
23396                .active_item(cx)
23397                .expect("Should have reopened the editor again after navigating back")
23398                .downcast::<Editor>()
23399                .expect("Should be an editor")
23400        })
23401        .unwrap();
23402    color_request_handle.next().await.unwrap();
23403    assert_eq!(
23404        3,
23405        requests_made.load(atomic::Ordering::Acquire),
23406        "Cache should be reused on buffer close and reopen"
23407    );
23408    editor.update(cx, |editor, cx| {
23409        assert_eq!(
23410            vec![expected_color],
23411            extract_color_inlays(editor, cx),
23412            "Should have an initial inlay"
23413        );
23414    });
23415}
23416
23417#[gpui::test]
23418async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23419    init_test(cx, |_| {});
23420    let (editor, cx) = cx.add_window_view(Editor::single_line);
23421    editor.update_in(cx, |editor, window, cx| {
23422        editor.set_text("oops\n\nwow\n", window, cx)
23423    });
23424    cx.run_until_parked();
23425    editor.update(cx, |editor, cx| {
23426        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23427    });
23428    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23429    cx.run_until_parked();
23430    editor.update(cx, |editor, cx| {
23431        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23432    });
23433}
23434
23435#[track_caller]
23436fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23437    editor
23438        .all_inlays(cx)
23439        .into_iter()
23440        .filter_map(|inlay| inlay.get_color())
23441        .map(Rgba::from)
23442        .collect()
23443}