editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    cx.set_state(indoc! {"
 4734        «implement-windows-supportˇ»
 4735    "});
 4736    cx.update_editor(|e, window, cx| {
 4737        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4738    });
 4739    cx.assert_editor_state(indoc! {"
 4740        «Implement windows supportˇ»
 4741    "});
 4742}
 4743
 4744#[gpui::test]
 4745async fn test_manipulate_text(cx: &mut TestAppContext) {
 4746    init_test(cx, |_| {});
 4747
 4748    let mut cx = EditorTestContext::new(cx).await;
 4749
 4750    // Test convert_to_upper_case()
 4751    cx.set_state(indoc! {"
 4752        «hello worldˇ»
 4753    "});
 4754    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4755    cx.assert_editor_state(indoc! {"
 4756        «HELLO WORLDˇ»
 4757    "});
 4758
 4759    // Test convert_to_lower_case()
 4760    cx.set_state(indoc! {"
 4761        «HELLO WORLDˇ»
 4762    "});
 4763    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4764    cx.assert_editor_state(indoc! {"
 4765        «hello worldˇ»
 4766    "});
 4767
 4768    // Test multiple line, single selection case
 4769    cx.set_state(indoc! {"
 4770        «The quick brown
 4771        fox jumps over
 4772        the lazy dogˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        «The Quick Brown
 4777        Fox Jumps Over
 4778        The Lazy Dogˇ»
 4779    "});
 4780
 4781    // Test multiple line, single selection case
 4782    cx.set_state(indoc! {"
 4783        «The quick brown
 4784        fox jumps over
 4785        the lazy dogˇ»
 4786    "});
 4787    cx.update_editor(|e, window, cx| {
 4788        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4789    });
 4790    cx.assert_editor_state(indoc! {"
 4791        «TheQuickBrown
 4792        FoxJumpsOver
 4793        TheLazyDogˇ»
 4794    "});
 4795
 4796    // From here on out, test more complex cases of manipulate_text()
 4797
 4798    // Test no selection case - should affect words cursors are in
 4799    // Cursor at beginning, middle, and end of word
 4800    cx.set_state(indoc! {"
 4801        ˇhello big beauˇtiful worldˇ
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4806    "});
 4807
 4808    // Test multiple selections on a single line and across multiple lines
 4809    cx.set_state(indoc! {"
 4810        «Theˇ» quick «brown
 4811        foxˇ» jumps «overˇ»
 4812        the «lazyˇ» dog
 4813    "});
 4814    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4815    cx.assert_editor_state(indoc! {"
 4816        «THEˇ» quick «BROWN
 4817        FOXˇ» jumps «OVERˇ»
 4818        the «LAZYˇ» dog
 4819    "});
 4820
 4821    // Test case where text length grows
 4822    cx.set_state(indoc! {"
 4823        «tschüߡ»
 4824    "});
 4825    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4826    cx.assert_editor_state(indoc! {"
 4827        «TSCHÜSSˇ»
 4828    "});
 4829
 4830    // Test to make sure we don't crash when text shrinks
 4831    cx.set_state(indoc! {"
 4832        aaa_bbbˇ
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «aaaBbbˇ»
 4839    "});
 4840
 4841    // Test to make sure we all aware of the fact that each word can grow and shrink
 4842    // Final selections should be aware of this fact
 4843    cx.set_state(indoc! {"
 4844        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4845    "});
 4846    cx.update_editor(|e, window, cx| {
 4847        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4848    });
 4849    cx.assert_editor_state(indoc! {"
 4850        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4851    "});
 4852
 4853    cx.set_state(indoc! {"
 4854        «hElLo, WoRld!ˇ»
 4855    "});
 4856    cx.update_editor(|e, window, cx| {
 4857        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4858    });
 4859    cx.assert_editor_state(indoc! {"
 4860        «HeLlO, wOrLD!ˇ»
 4861    "});
 4862}
 4863
 4864#[gpui::test]
 4865fn test_duplicate_line(cx: &mut TestAppContext) {
 4866    init_test(cx, |_| {});
 4867
 4868    let editor = cx.add_window(|window, cx| {
 4869        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4870        build_editor(buffer, window, cx)
 4871    });
 4872    _ = editor.update(cx, |editor, window, cx| {
 4873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4874            s.select_display_ranges([
 4875                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4876                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4877                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4878                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4879            ])
 4880        });
 4881        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4882        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4883        assert_eq!(
 4884            editor.selections.display_ranges(cx),
 4885            vec![
 4886                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4887                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4888                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4889                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4890            ]
 4891        );
 4892    });
 4893
 4894    let editor = cx.add_window(|window, cx| {
 4895        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4896        build_editor(buffer, window, cx)
 4897    });
 4898    _ = editor.update(cx, |editor, window, cx| {
 4899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4900            s.select_display_ranges([
 4901                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4902                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4903            ])
 4904        });
 4905        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4906        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4907        assert_eq!(
 4908            editor.selections.display_ranges(cx),
 4909            vec![
 4910                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4911                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4912            ]
 4913        );
 4914    });
 4915
 4916    // With `move_upwards` the selections stay in place, except for
 4917    // the lines inserted above them
 4918    let editor = cx.add_window(|window, cx| {
 4919        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4920        build_editor(buffer, window, cx)
 4921    });
 4922    _ = editor.update(cx, |editor, window, cx| {
 4923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4924            s.select_display_ranges([
 4925                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4926                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4927                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4928                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4929            ])
 4930        });
 4931        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4932        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4933        assert_eq!(
 4934            editor.selections.display_ranges(cx),
 4935            vec![
 4936                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4937                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4938                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4939                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4962            ]
 4963        );
 4964    });
 4965
 4966    let editor = cx.add_window(|window, cx| {
 4967        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4968        build_editor(buffer, window, cx)
 4969    });
 4970    _ = editor.update(cx, |editor, window, cx| {
 4971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4972            s.select_display_ranges([
 4973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4974                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4975            ])
 4976        });
 4977        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4978        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4979        assert_eq!(
 4980            editor.selections.display_ranges(cx),
 4981            vec![
 4982                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4983                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4984            ]
 4985        );
 4986    });
 4987}
 4988
 4989#[gpui::test]
 4990fn test_move_line_up_down(cx: &mut TestAppContext) {
 4991    init_test(cx, |_| {});
 4992
 4993    let editor = cx.add_window(|window, cx| {
 4994        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4995        build_editor(buffer, window, cx)
 4996    });
 4997    _ = editor.update(cx, |editor, window, cx| {
 4998        editor.fold_creases(
 4999            vec![
 5000                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5001                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5002                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5003            ],
 5004            true,
 5005            window,
 5006            cx,
 5007        );
 5008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5009            s.select_display_ranges([
 5010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5011                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5012                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5013                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5014            ])
 5015        });
 5016        assert_eq!(
 5017            editor.display_text(cx),
 5018            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5019        );
 5020
 5021        editor.move_line_up(&MoveLineUp, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5030                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5031                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5032                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_down(&MoveLineDown, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5064                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5065                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5066                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5067            ]
 5068        );
 5069    });
 5070
 5071    _ = editor.update(cx, |editor, window, cx| {
 5072        editor.move_line_up(&MoveLineUp, window, cx);
 5073        assert_eq!(
 5074            editor.display_text(cx),
 5075            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5076        );
 5077        assert_eq!(
 5078            editor.selections.display_ranges(cx),
 5079            vec![
 5080                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5081                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5082                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5083                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5084            ]
 5085        );
 5086    });
 5087}
 5088
 5089#[gpui::test]
 5090fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5091    init_test(cx, |_| {});
 5092    let editor = cx.add_window(|window, cx| {
 5093        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5094        build_editor(buffer, window, cx)
 5095    });
 5096    _ = editor.update(cx, |editor, window, cx| {
 5097        editor.fold_creases(
 5098            vec![Crease::simple(
 5099                Point::new(6, 4)..Point::new(7, 4),
 5100                FoldPlaceholder::test(),
 5101            )],
 5102            true,
 5103            window,
 5104            cx,
 5105        );
 5106        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5107            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5108        });
 5109        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5110        editor.move_line_up(&MoveLineUp, window, cx);
 5111        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5112        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5113    });
 5114}
 5115
 5116#[gpui::test]
 5117fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5118    init_test(cx, |_| {});
 5119
 5120    let editor = cx.add_window(|window, cx| {
 5121        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5122        build_editor(buffer, window, cx)
 5123    });
 5124    _ = editor.update(cx, |editor, window, cx| {
 5125        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5126        editor.insert_blocks(
 5127            [BlockProperties {
 5128                style: BlockStyle::Fixed,
 5129                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5130                height: Some(1),
 5131                render: Arc::new(|_| div().into_any()),
 5132                priority: 0,
 5133            }],
 5134            Some(Autoscroll::fit()),
 5135            cx,
 5136        );
 5137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5138            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5139        });
 5140        editor.move_line_down(&MoveLineDown, window, cx);
 5141    });
 5142}
 5143
 5144#[gpui::test]
 5145async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5146    init_test(cx, |_| {});
 5147
 5148    let mut cx = EditorTestContext::new(cx).await;
 5149    cx.set_state(
 5150        &"
 5151            ˇzero
 5152            one
 5153            two
 5154            three
 5155            four
 5156            five
 5157        "
 5158        .unindent(),
 5159    );
 5160
 5161    // Create a four-line block that replaces three lines of text.
 5162    cx.update_editor(|editor, window, cx| {
 5163        let snapshot = editor.snapshot(window, cx);
 5164        let snapshot = &snapshot.buffer_snapshot;
 5165        let placement = BlockPlacement::Replace(
 5166            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5167        );
 5168        editor.insert_blocks(
 5169            [BlockProperties {
 5170                placement,
 5171                height: Some(4),
 5172                style: BlockStyle::Sticky,
 5173                render: Arc::new(|_| gpui::div().into_any_element()),
 5174                priority: 0,
 5175            }],
 5176            None,
 5177            cx,
 5178        );
 5179    });
 5180
 5181    // Move down so that the cursor touches the block.
 5182    cx.update_editor(|editor, window, cx| {
 5183        editor.move_down(&Default::default(), window, cx);
 5184    });
 5185    cx.assert_editor_state(
 5186        &"
 5187            zero
 5188            «one
 5189            two
 5190            threeˇ»
 5191            four
 5192            five
 5193        "
 5194        .unindent(),
 5195    );
 5196
 5197    // Move down past the block.
 5198    cx.update_editor(|editor, window, cx| {
 5199        editor.move_down(&Default::default(), window, cx);
 5200    });
 5201    cx.assert_editor_state(
 5202        &"
 5203            zero
 5204            one
 5205            two
 5206            three
 5207            ˇfour
 5208            five
 5209        "
 5210        .unindent(),
 5211    );
 5212}
 5213
 5214#[gpui::test]
 5215fn test_transpose(cx: &mut TestAppContext) {
 5216    init_test(cx, |_| {});
 5217
 5218    _ = cx.add_window(|window, cx| {
 5219        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5220        editor.set_style(EditorStyle::default(), window, cx);
 5221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5222            s.select_ranges([1..1])
 5223        });
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "bac");
 5226        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5227
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "bca");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5231
 5232        editor.transpose(&Default::default(), window, cx);
 5233        assert_eq!(editor.text(cx), "bac");
 5234        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5235
 5236        editor
 5237    });
 5238
 5239    _ = cx.add_window(|window, cx| {
 5240        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5241        editor.set_style(EditorStyle::default(), window, cx);
 5242        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5243            s.select_ranges([3..3])
 5244        });
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "acb\nde");
 5247        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5248
 5249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5250            s.select_ranges([4..4])
 5251        });
 5252        editor.transpose(&Default::default(), window, cx);
 5253        assert_eq!(editor.text(cx), "acbd\ne");
 5254        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5255
 5256        editor.transpose(&Default::default(), window, cx);
 5257        assert_eq!(editor.text(cx), "acbde\n");
 5258        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5259
 5260        editor.transpose(&Default::default(), window, cx);
 5261        assert_eq!(editor.text(cx), "acbd\ne");
 5262        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5263
 5264        editor
 5265    });
 5266
 5267    _ = cx.add_window(|window, cx| {
 5268        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5269        editor.set_style(EditorStyle::default(), window, cx);
 5270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5271            s.select_ranges([1..1, 2..2, 4..4])
 5272        });
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bacd\ne");
 5275        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bcade\n");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5280
 5281        editor.transpose(&Default::default(), window, cx);
 5282        assert_eq!(editor.text(cx), "bcda\ne");
 5283        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5284
 5285        editor.transpose(&Default::default(), window, cx);
 5286        assert_eq!(editor.text(cx), "bcade\n");
 5287        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5288
 5289        editor.transpose(&Default::default(), window, cx);
 5290        assert_eq!(editor.text(cx), "bcaed\n");
 5291        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5292
 5293        editor
 5294    });
 5295
 5296    _ = cx.add_window(|window, cx| {
 5297        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5298        editor.set_style(EditorStyle::default(), window, cx);
 5299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5300            s.select_ranges([4..4])
 5301        });
 5302        editor.transpose(&Default::default(), window, cx);
 5303        assert_eq!(editor.text(cx), "🏀🍐✋");
 5304        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5305
 5306        editor.transpose(&Default::default(), window, cx);
 5307        assert_eq!(editor.text(cx), "🏀✋🍐");
 5308        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5309
 5310        editor.transpose(&Default::default(), window, cx);
 5311        assert_eq!(editor.text(cx), "🏀🍐✋");
 5312        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5313
 5314        editor
 5315    });
 5316}
 5317
 5318#[gpui::test]
 5319async fn test_rewrap(cx: &mut TestAppContext) {
 5320    init_test(cx, |settings| {
 5321        settings.languages.0.extend([
 5322            (
 5323                "Markdown".into(),
 5324                LanguageSettingsContent {
 5325                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5326                    preferred_line_length: Some(40),
 5327                    ..Default::default()
 5328                },
 5329            ),
 5330            (
 5331                "Plain Text".into(),
 5332                LanguageSettingsContent {
 5333                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5334                    preferred_line_length: Some(40),
 5335                    ..Default::default()
 5336                },
 5337            ),
 5338            (
 5339                "C++".into(),
 5340                LanguageSettingsContent {
 5341                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5342                    preferred_line_length: Some(40),
 5343                    ..Default::default()
 5344                },
 5345            ),
 5346            (
 5347                "Python".into(),
 5348                LanguageSettingsContent {
 5349                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5350                    preferred_line_length: Some(40),
 5351                    ..Default::default()
 5352                },
 5353            ),
 5354            (
 5355                "Rust".into(),
 5356                LanguageSettingsContent {
 5357                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5358                    preferred_line_length: Some(40),
 5359                    ..Default::default()
 5360                },
 5361            ),
 5362        ])
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    let cpp_language = Arc::new(Language::new(
 5368        LanguageConfig {
 5369            name: "C++".into(),
 5370            line_comments: vec!["// ".into()],
 5371            ..LanguageConfig::default()
 5372        },
 5373        None,
 5374    ));
 5375    let python_language = Arc::new(Language::new(
 5376        LanguageConfig {
 5377            name: "Python".into(),
 5378            line_comments: vec!["# ".into()],
 5379            ..LanguageConfig::default()
 5380        },
 5381        None,
 5382    ));
 5383    let markdown_language = Arc::new(Language::new(
 5384        LanguageConfig {
 5385            name: "Markdown".into(),
 5386            rewrap_prefixes: vec![
 5387                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5388                regex::Regex::new("[-*+]\\s+").unwrap(),
 5389            ],
 5390            ..LanguageConfig::default()
 5391        },
 5392        None,
 5393    ));
 5394    let rust_language = Arc::new(Language::new(
 5395        LanguageConfig {
 5396            name: "Rust".into(),
 5397            line_comments: vec!["// ".into(), "/// ".into()],
 5398            ..LanguageConfig::default()
 5399        },
 5400        Some(tree_sitter_rust::LANGUAGE.into()),
 5401    ));
 5402
 5403    let plaintext_language = Arc::new(Language::new(
 5404        LanguageConfig {
 5405            name: "Plain Text".into(),
 5406            ..LanguageConfig::default()
 5407        },
 5408        None,
 5409    ));
 5410
 5411    // Test basic rewrapping of a long line with a cursor
 5412    assert_rewrap(
 5413        indoc! {"
 5414            // ˇThis is a long comment that needs to be wrapped.
 5415        "},
 5416        indoc! {"
 5417            // ˇThis is a long comment that needs to
 5418            // be wrapped.
 5419        "},
 5420        cpp_language.clone(),
 5421        &mut cx,
 5422    );
 5423
 5424    // Test rewrapping a full selection
 5425    assert_rewrap(
 5426        indoc! {"
 5427            «// This selected long comment needs to be wrapped.ˇ»"
 5428        },
 5429        indoc! {"
 5430            «// This selected long comment needs to
 5431            // be wrapped.ˇ»"
 5432        },
 5433        cpp_language.clone(),
 5434        &mut cx,
 5435    );
 5436
 5437    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5438    assert_rewrap(
 5439        indoc! {"
 5440            // ˇThis is the first line.
 5441            // Thisˇ is the second line.
 5442            // This is the thirdˇ line, all part of one paragraph.
 5443         "},
 5444        indoc! {"
 5445            // ˇThis is the first line. Thisˇ is the
 5446            // second line. This is the thirdˇ line,
 5447            // all part of one paragraph.
 5448         "},
 5449        cpp_language.clone(),
 5450        &mut cx,
 5451    );
 5452
 5453    // Test multiple cursors in different paragraphs trigger separate rewraps
 5454    assert_rewrap(
 5455        indoc! {"
 5456            // ˇThis is the first paragraph, first line.
 5457            // ˇThis is the first paragraph, second line.
 5458
 5459            // ˇThis is the second paragraph, first line.
 5460            // ˇThis is the second paragraph, second line.
 5461        "},
 5462        indoc! {"
 5463            // ˇThis is the first paragraph, first
 5464            // line. ˇThis is the first paragraph,
 5465            // second line.
 5466
 5467            // ˇThis is the second paragraph, first
 5468            // line. ˇThis is the second paragraph,
 5469            // second line.
 5470        "},
 5471        cpp_language.clone(),
 5472        &mut cx,
 5473    );
 5474
 5475    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5476    assert_rewrap(
 5477        indoc! {"
 5478            «// A regular long long comment to be wrapped.
 5479            /// A documentation long comment to be wrapped.ˇ»
 5480          "},
 5481        indoc! {"
 5482            «// A regular long long comment to be
 5483            // wrapped.
 5484            /// A documentation long comment to be
 5485            /// wrapped.ˇ»
 5486          "},
 5487        rust_language.clone(),
 5488        &mut cx,
 5489    );
 5490
 5491    // Test that change in indentation level trigger seperate rewraps
 5492    assert_rewrap(
 5493        indoc! {"
 5494            fn foo() {
 5495                «// This is a long comment at the base indent.
 5496                    // This is a long comment at the next indent.ˇ»
 5497            }
 5498        "},
 5499        indoc! {"
 5500            fn foo() {
 5501                «// This is a long comment at the
 5502                // base indent.
 5503                    // This is a long comment at the
 5504                    // next indent.ˇ»
 5505            }
 5506        "},
 5507        rust_language.clone(),
 5508        &mut cx,
 5509    );
 5510
 5511    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5512    assert_rewrap(
 5513        indoc! {"
 5514            # ˇThis is a long comment using a pound sign.
 5515        "},
 5516        indoc! {"
 5517            # ˇThis is a long comment using a pound
 5518            # sign.
 5519        "},
 5520        python_language.clone(),
 5521        &mut cx,
 5522    );
 5523
 5524    // Test rewrapping only affects comments, not code even when selected
 5525    assert_rewrap(
 5526        indoc! {"
 5527            «/// This doc comment is long and should be wrapped.
 5528            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5529        "},
 5530        indoc! {"
 5531            «/// This doc comment is long and should
 5532            /// be wrapped.
 5533            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5534        "},
 5535        rust_language.clone(),
 5536        &mut cx,
 5537    );
 5538
 5539    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5540    assert_rewrap(
 5541        indoc! {"
 5542            # Header
 5543
 5544            A long long long line of markdown text to wrap.ˇ
 5545         "},
 5546        indoc! {"
 5547            # Header
 5548
 5549            A long long long line of markdown text
 5550            to wrap.ˇ
 5551         "},
 5552        markdown_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5557    assert_rewrap(
 5558        indoc! {"
 5559            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5560            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5561            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5562        "},
 5563        indoc! {"
 5564            «1. This is a numbered list item that is
 5565               very long and needs to be wrapped
 5566               properly.
 5567            2. This is a numbered list item that is
 5568               very long and needs to be wrapped
 5569               properly.
 5570            - This is an unordered list item that is
 5571              also very long and should not merge
 5572              with the numbered item.ˇ»
 5573        "},
 5574        markdown_language.clone(),
 5575        &mut cx,
 5576    );
 5577
 5578    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5579    assert_rewrap(
 5580        indoc! {"
 5581            «1. This is a numbered list item that is
 5582            very long and needs to be wrapped
 5583            properly.
 5584            2. This is a numbered list item that is
 5585            very long and needs to be wrapped
 5586            properly.
 5587            - This is an unordered list item that is
 5588            also very long and should not merge with
 5589            the numbered item.ˇ»
 5590        "},
 5591        indoc! {"
 5592            «1. This is a numbered list item that is
 5593               very long and needs to be wrapped
 5594               properly.
 5595            2. This is a numbered list item that is
 5596               very long and needs to be wrapped
 5597               properly.
 5598            - This is an unordered list item that is
 5599              also very long and should not merge
 5600              with the numbered item.ˇ»
 5601        "},
 5602        markdown_language.clone(),
 5603        &mut cx,
 5604    );
 5605
 5606    // Test that rewrapping maintain indents even when they already exists.
 5607    assert_rewrap(
 5608        indoc! {"
 5609            «1. This is a numbered list
 5610               item that is very long and needs to be wrapped properly.
 5611            2. This is a numbered list
 5612               item that is very long and needs to be wrapped properly.
 5613            - This is an unordered list item that is also very long and
 5614              should not merge with the numbered item.ˇ»
 5615        "},
 5616        indoc! {"
 5617            «1. This is a numbered list item that is
 5618               very long and needs to be wrapped
 5619               properly.
 5620            2. This is a numbered list item that is
 5621               very long and needs to be wrapped
 5622               properly.
 5623            - This is an unordered list item that is
 5624              also very long and should not merge
 5625              with the numbered item.ˇ»
 5626        "},
 5627        markdown_language.clone(),
 5628        &mut cx,
 5629    );
 5630
 5631    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5632    assert_rewrap(
 5633        indoc! {"
 5634            ˇThis is a very long line of plain text that will be wrapped.
 5635        "},
 5636        indoc! {"
 5637            ˇThis is a very long line of plain text
 5638            that will be wrapped.
 5639        "},
 5640        plaintext_language.clone(),
 5641        &mut cx,
 5642    );
 5643
 5644    // Test that non-commented code acts as a paragraph boundary within a selection
 5645    assert_rewrap(
 5646        indoc! {"
 5647               «// This is the first long comment block to be wrapped.
 5648               fn my_func(a: u32);
 5649               // This is the second long comment block to be wrapped.ˇ»
 5650           "},
 5651        indoc! {"
 5652               «// This is the first long comment block
 5653               // to be wrapped.
 5654               fn my_func(a: u32);
 5655               // This is the second long comment block
 5656               // to be wrapped.ˇ»
 5657           "},
 5658        rust_language.clone(),
 5659        &mut cx,
 5660    );
 5661
 5662    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5663    assert_rewrap(
 5664        indoc! {"
 5665            «ˇThis is a very long line that will be wrapped.
 5666
 5667            This is another paragraph in the same selection.»
 5668
 5669            «\tThis is a very long indented line that will be wrapped.ˇ»
 5670         "},
 5671        indoc! {"
 5672            «ˇThis is a very long line that will be
 5673            wrapped.
 5674
 5675            This is another paragraph in the same
 5676            selection.»
 5677
 5678            «\tThis is a very long indented line
 5679            \tthat will be wrapped.ˇ»
 5680         "},
 5681        plaintext_language.clone(),
 5682        &mut cx,
 5683    );
 5684
 5685    // Test that an empty comment line acts as a paragraph boundary
 5686    assert_rewrap(
 5687        indoc! {"
 5688            // ˇThis is a long comment that will be wrapped.
 5689            //
 5690            // And this is another long comment that will also be wrapped.ˇ
 5691         "},
 5692        indoc! {"
 5693            // ˇThis is a long comment that will be
 5694            // wrapped.
 5695            //
 5696            // And this is another long comment that
 5697            // will also be wrapped.ˇ
 5698         "},
 5699        cpp_language,
 5700        &mut cx,
 5701    );
 5702
 5703    #[track_caller]
 5704    fn assert_rewrap(
 5705        unwrapped_text: &str,
 5706        wrapped_text: &str,
 5707        language: Arc<Language>,
 5708        cx: &mut EditorTestContext,
 5709    ) {
 5710        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5711        cx.set_state(unwrapped_text);
 5712        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5713        cx.assert_editor_state(wrapped_text);
 5714    }
 5715}
 5716
 5717#[gpui::test]
 5718async fn test_hard_wrap(cx: &mut TestAppContext) {
 5719    init_test(cx, |_| {});
 5720    let mut cx = EditorTestContext::new(cx).await;
 5721
 5722    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5723    cx.update_editor(|editor, _, cx| {
 5724        editor.set_hard_wrap(Some(14), cx);
 5725    });
 5726
 5727    cx.set_state(indoc!(
 5728        "
 5729        one two three ˇ
 5730        "
 5731    ));
 5732    cx.simulate_input("four");
 5733    cx.run_until_parked();
 5734
 5735    cx.assert_editor_state(indoc!(
 5736        "
 5737        one two three
 5738        fourˇ
 5739        "
 5740    ));
 5741
 5742    cx.update_editor(|editor, window, cx| {
 5743        editor.newline(&Default::default(), window, cx);
 5744    });
 5745    cx.run_until_parked();
 5746    cx.assert_editor_state(indoc!(
 5747        "
 5748        one two three
 5749        four
 5750        ˇ
 5751        "
 5752    ));
 5753
 5754    cx.simulate_input("five");
 5755    cx.run_until_parked();
 5756    cx.assert_editor_state(indoc!(
 5757        "
 5758        one two three
 5759        four
 5760        fiveˇ
 5761        "
 5762    ));
 5763
 5764    cx.update_editor(|editor, window, cx| {
 5765        editor.newline(&Default::default(), window, cx);
 5766    });
 5767    cx.run_until_parked();
 5768    cx.simulate_input("# ");
 5769    cx.run_until_parked();
 5770    cx.assert_editor_state(indoc!(
 5771        "
 5772        one two three
 5773        four
 5774        five
 5775        # ˇ
 5776        "
 5777    ));
 5778
 5779    cx.update_editor(|editor, window, cx| {
 5780        editor.newline(&Default::default(), window, cx);
 5781    });
 5782    cx.run_until_parked();
 5783    cx.assert_editor_state(indoc!(
 5784        "
 5785        one two three
 5786        four
 5787        five
 5788        #\x20
 5789 5790        "
 5791    ));
 5792
 5793    cx.simulate_input(" 6");
 5794    cx.run_until_parked();
 5795    cx.assert_editor_state(indoc!(
 5796        "
 5797        one two three
 5798        four
 5799        five
 5800        #
 5801        # 6ˇ
 5802        "
 5803    ));
 5804}
 5805
 5806#[gpui::test]
 5807async fn test_clipboard(cx: &mut TestAppContext) {
 5808    init_test(cx, |_| {});
 5809
 5810    let mut cx = EditorTestContext::new(cx).await;
 5811
 5812    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5813    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5814    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5815
 5816    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5817    cx.set_state("two ˇfour ˇsix ˇ");
 5818    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5819    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5820
 5821    // Paste again but with only two cursors. Since the number of cursors doesn't
 5822    // match the number of slices in the clipboard, the entire clipboard text
 5823    // is pasted at each cursor.
 5824    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5825    cx.update_editor(|e, window, cx| {
 5826        e.handle_input("( ", window, cx);
 5827        e.paste(&Paste, window, cx);
 5828        e.handle_input(") ", window, cx);
 5829    });
 5830    cx.assert_editor_state(
 5831        &([
 5832            "( one✅ ",
 5833            "three ",
 5834            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5835            "three ",
 5836            "five ) ˇ",
 5837        ]
 5838        .join("\n")),
 5839    );
 5840
 5841    // Cut with three selections, one of which is full-line.
 5842    cx.set_state(indoc! {"
 5843        1«2ˇ»3
 5844        4ˇ567
 5845        «8ˇ»9"});
 5846    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5847    cx.assert_editor_state(indoc! {"
 5848        1ˇ3
 5849        ˇ9"});
 5850
 5851    // Paste with three selections, noticing how the copied selection that was full-line
 5852    // gets inserted before the second cursor.
 5853    cx.set_state(indoc! {"
 5854        1ˇ3
 5855 5856        «oˇ»ne"});
 5857    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5858    cx.assert_editor_state(indoc! {"
 5859        12ˇ3
 5860        4567
 5861 5862        8ˇne"});
 5863
 5864    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5865    cx.set_state(indoc! {"
 5866        The quick brown
 5867        fox juˇmps over
 5868        the lazy dog"});
 5869    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5870    assert_eq!(
 5871        cx.read_from_clipboard()
 5872            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5873        Some("fox jumps over\n".to_string())
 5874    );
 5875
 5876    // Paste with three selections, noticing how the copied full-line selection is inserted
 5877    // before the empty selections but replaces the selection that is non-empty.
 5878    cx.set_state(indoc! {"
 5879        Tˇhe quick brown
 5880        «foˇ»x jumps over
 5881        tˇhe lazy dog"});
 5882    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5883    cx.assert_editor_state(indoc! {"
 5884        fox jumps over
 5885        Tˇhe quick brown
 5886        fox jumps over
 5887        ˇx jumps over
 5888        fox jumps over
 5889        tˇhe lazy dog"});
 5890}
 5891
 5892#[gpui::test]
 5893async fn test_copy_trim(cx: &mut TestAppContext) {
 5894    init_test(cx, |_| {});
 5895
 5896    let mut cx = EditorTestContext::new(cx).await;
 5897    cx.set_state(
 5898        r#"            «for selection in selections.iter() {
 5899            let mut start = selection.start;
 5900            let mut end = selection.end;
 5901            let is_entire_line = selection.is_empty();
 5902            if is_entire_line {
 5903                start = Point::new(start.row, 0);ˇ»
 5904                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5905            }
 5906        "#,
 5907    );
 5908    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5909    assert_eq!(
 5910        cx.read_from_clipboard()
 5911            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5912        Some(
 5913            "for selection in selections.iter() {
 5914            let mut start = selection.start;
 5915            let mut end = selection.end;
 5916            let is_entire_line = selection.is_empty();
 5917            if is_entire_line {
 5918                start = Point::new(start.row, 0);"
 5919                .to_string()
 5920        ),
 5921        "Regular copying preserves all indentation selected",
 5922    );
 5923    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5924    assert_eq!(
 5925        cx.read_from_clipboard()
 5926            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5927        Some(
 5928            "for selection in selections.iter() {
 5929let mut start = selection.start;
 5930let mut end = selection.end;
 5931let is_entire_line = selection.is_empty();
 5932if is_entire_line {
 5933    start = Point::new(start.row, 0);"
 5934                .to_string()
 5935        ),
 5936        "Copying with stripping should strip all leading whitespaces"
 5937    );
 5938
 5939    cx.set_state(
 5940        r#"       «     for selection in selections.iter() {
 5941            let mut start = selection.start;
 5942            let mut end = selection.end;
 5943            let is_entire_line = selection.is_empty();
 5944            if is_entire_line {
 5945                start = Point::new(start.row, 0);ˇ»
 5946                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5947            }
 5948        "#,
 5949    );
 5950    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5951    assert_eq!(
 5952        cx.read_from_clipboard()
 5953            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5954        Some(
 5955            "     for selection in selections.iter() {
 5956            let mut start = selection.start;
 5957            let mut end = selection.end;
 5958            let is_entire_line = selection.is_empty();
 5959            if is_entire_line {
 5960                start = Point::new(start.row, 0);"
 5961                .to_string()
 5962        ),
 5963        "Regular copying preserves all indentation selected",
 5964    );
 5965    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5966    assert_eq!(
 5967        cx.read_from_clipboard()
 5968            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5969        Some(
 5970            "for selection in selections.iter() {
 5971let mut start = selection.start;
 5972let mut end = selection.end;
 5973let is_entire_line = selection.is_empty();
 5974if is_entire_line {
 5975    start = Point::new(start.row, 0);"
 5976                .to_string()
 5977        ),
 5978        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5979    );
 5980
 5981    cx.set_state(
 5982        r#"       «ˇ     for selection in selections.iter() {
 5983            let mut start = selection.start;
 5984            let mut end = selection.end;
 5985            let is_entire_line = selection.is_empty();
 5986            if is_entire_line {
 5987                start = Point::new(start.row, 0);»
 5988                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5989            }
 5990        "#,
 5991    );
 5992    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5993    assert_eq!(
 5994        cx.read_from_clipboard()
 5995            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5996        Some(
 5997            "     for selection in selections.iter() {
 5998            let mut start = selection.start;
 5999            let mut end = selection.end;
 6000            let is_entire_line = selection.is_empty();
 6001            if is_entire_line {
 6002                start = Point::new(start.row, 0);"
 6003                .to_string()
 6004        ),
 6005        "Regular copying for reverse selection works the same",
 6006    );
 6007    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6008    assert_eq!(
 6009        cx.read_from_clipboard()
 6010            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6011        Some(
 6012            "for selection in selections.iter() {
 6013let mut start = selection.start;
 6014let mut end = selection.end;
 6015let is_entire_line = selection.is_empty();
 6016if is_entire_line {
 6017    start = Point::new(start.row, 0);"
 6018                .to_string()
 6019        ),
 6020        "Copying with stripping for reverse selection works the same"
 6021    );
 6022
 6023    cx.set_state(
 6024        r#"            for selection «in selections.iter() {
 6025            let mut start = selection.start;
 6026            let mut end = selection.end;
 6027            let is_entire_line = selection.is_empty();
 6028            if is_entire_line {
 6029                start = Point::new(start.row, 0);ˇ»
 6030                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6031            }
 6032        "#,
 6033    );
 6034    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6035    assert_eq!(
 6036        cx.read_from_clipboard()
 6037            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6038        Some(
 6039            "in selections.iter() {
 6040            let mut start = selection.start;
 6041            let mut end = selection.end;
 6042            let is_entire_line = selection.is_empty();
 6043            if is_entire_line {
 6044                start = Point::new(start.row, 0);"
 6045                .to_string()
 6046        ),
 6047        "When selecting past the indent, the copying works as usual",
 6048    );
 6049    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6050    assert_eq!(
 6051        cx.read_from_clipboard()
 6052            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6053        Some(
 6054            "in selections.iter() {
 6055            let mut start = selection.start;
 6056            let mut end = selection.end;
 6057            let is_entire_line = selection.is_empty();
 6058            if is_entire_line {
 6059                start = Point::new(start.row, 0);"
 6060                .to_string()
 6061        ),
 6062        "When selecting past the indent, nothing is trimmed"
 6063    );
 6064
 6065    cx.set_state(
 6066        r#"            «for selection in selections.iter() {
 6067            let mut start = selection.start;
 6068
 6069            let mut end = selection.end;
 6070            let is_entire_line = selection.is_empty();
 6071            if is_entire_line {
 6072                start = Point::new(start.row, 0);
 6073ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6074            }
 6075        "#,
 6076    );
 6077    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6078    assert_eq!(
 6079        cx.read_from_clipboard()
 6080            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6081        Some(
 6082            "for selection in selections.iter() {
 6083let mut start = selection.start;
 6084
 6085let mut end = selection.end;
 6086let is_entire_line = selection.is_empty();
 6087if is_entire_line {
 6088    start = Point::new(start.row, 0);
 6089"
 6090            .to_string()
 6091        ),
 6092        "Copying with stripping should ignore empty lines"
 6093    );
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_paste_multiline(cx: &mut TestAppContext) {
 6098    init_test(cx, |_| {});
 6099
 6100    let mut cx = EditorTestContext::new(cx).await;
 6101    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6102
 6103    // Cut an indented block, without the leading whitespace.
 6104    cx.set_state(indoc! {"
 6105        const a: B = (
 6106            c(),
 6107            «d(
 6108                e,
 6109                f
 6110            )ˇ»
 6111        );
 6112    "});
 6113    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6114    cx.assert_editor_state(indoc! {"
 6115        const a: B = (
 6116            c(),
 6117            ˇ
 6118        );
 6119    "});
 6120
 6121    // Paste it at the same position.
 6122    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6123    cx.assert_editor_state(indoc! {"
 6124        const a: B = (
 6125            c(),
 6126            d(
 6127                e,
 6128                f
 6129 6130        );
 6131    "});
 6132
 6133    // Paste it at a line with a lower indent level.
 6134    cx.set_state(indoc! {"
 6135        ˇ
 6136        const a: B = (
 6137            c(),
 6138        );
 6139    "});
 6140    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6141    cx.assert_editor_state(indoc! {"
 6142        d(
 6143            e,
 6144            f
 6145 6146        const a: B = (
 6147            c(),
 6148        );
 6149    "});
 6150
 6151    // Cut an indented block, with the leading whitespace.
 6152    cx.set_state(indoc! {"
 6153        const a: B = (
 6154            c(),
 6155        «    d(
 6156                e,
 6157                f
 6158            )
 6159        ˇ»);
 6160    "});
 6161    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6162    cx.assert_editor_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165        ˇ);
 6166    "});
 6167
 6168    // Paste it at the same position.
 6169    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6170    cx.assert_editor_state(indoc! {"
 6171        const a: B = (
 6172            c(),
 6173            d(
 6174                e,
 6175                f
 6176            )
 6177        ˇ);
 6178    "});
 6179
 6180    // Paste it at a line with a higher indent level.
 6181    cx.set_state(indoc! {"
 6182        const a: B = (
 6183            c(),
 6184            d(
 6185                e,
 6186 6187            )
 6188        );
 6189    "});
 6190    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6191    cx.assert_editor_state(indoc! {"
 6192        const a: B = (
 6193            c(),
 6194            d(
 6195                e,
 6196                f    d(
 6197                    e,
 6198                    f
 6199                )
 6200        ˇ
 6201            )
 6202        );
 6203    "});
 6204
 6205    // Copy an indented block, starting mid-line
 6206    cx.set_state(indoc! {"
 6207        const a: B = (
 6208            c(),
 6209            somethin«g(
 6210                e,
 6211                f
 6212            )ˇ»
 6213        );
 6214    "});
 6215    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6216
 6217    // Paste it on a line with a lower indent level
 6218    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6219    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6220    cx.assert_editor_state(indoc! {"
 6221        const a: B = (
 6222            c(),
 6223            something(
 6224                e,
 6225                f
 6226            )
 6227        );
 6228        g(
 6229            e,
 6230            f
 6231"});
 6232}
 6233
 6234#[gpui::test]
 6235async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6236    init_test(cx, |_| {});
 6237
 6238    cx.write_to_clipboard(ClipboardItem::new_string(
 6239        "    d(\n        e\n    );\n".into(),
 6240    ));
 6241
 6242    let mut cx = EditorTestContext::new(cx).await;
 6243    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6244
 6245    cx.set_state(indoc! {"
 6246        fn a() {
 6247            b();
 6248            if c() {
 6249                ˇ
 6250            }
 6251        }
 6252    "});
 6253
 6254    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6255    cx.assert_editor_state(indoc! {"
 6256        fn a() {
 6257            b();
 6258            if c() {
 6259                d(
 6260                    e
 6261                );
 6262        ˇ
 6263            }
 6264        }
 6265    "});
 6266
 6267    cx.set_state(indoc! {"
 6268        fn a() {
 6269            b();
 6270            ˇ
 6271        }
 6272    "});
 6273
 6274    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6275    cx.assert_editor_state(indoc! {"
 6276        fn a() {
 6277            b();
 6278            d(
 6279                e
 6280            );
 6281        ˇ
 6282        }
 6283    "});
 6284}
 6285
 6286#[gpui::test]
 6287fn test_select_all(cx: &mut TestAppContext) {
 6288    init_test(cx, |_| {});
 6289
 6290    let editor = cx.add_window(|window, cx| {
 6291        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6292        build_editor(buffer, window, cx)
 6293    });
 6294    _ = editor.update(cx, |editor, window, cx| {
 6295        editor.select_all(&SelectAll, window, cx);
 6296        assert_eq!(
 6297            editor.selections.display_ranges(cx),
 6298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6299        );
 6300    });
 6301}
 6302
 6303#[gpui::test]
 6304fn test_select_line(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306
 6307    let editor = cx.add_window(|window, cx| {
 6308        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6309        build_editor(buffer, window, cx)
 6310    });
 6311    _ = editor.update(cx, |editor, window, cx| {
 6312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6313            s.select_display_ranges([
 6314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6315                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6316                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6317                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6318            ])
 6319        });
 6320        editor.select_line(&SelectLine, window, cx);
 6321        assert_eq!(
 6322            editor.selections.display_ranges(cx),
 6323            vec![
 6324                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6325                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6326            ]
 6327        );
 6328    });
 6329
 6330    _ = editor.update(cx, |editor, window, cx| {
 6331        editor.select_line(&SelectLine, window, cx);
 6332        assert_eq!(
 6333            editor.selections.display_ranges(cx),
 6334            vec![
 6335                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6336                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6337            ]
 6338        );
 6339    });
 6340
 6341    _ = editor.update(cx, |editor, window, cx| {
 6342        editor.select_line(&SelectLine, window, cx);
 6343        assert_eq!(
 6344            editor.selections.display_ranges(cx),
 6345            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6346        );
 6347    });
 6348}
 6349
 6350#[gpui::test]
 6351async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6352    init_test(cx, |_| {});
 6353    let mut cx = EditorTestContext::new(cx).await;
 6354
 6355    #[track_caller]
 6356    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6357        cx.set_state(initial_state);
 6358        cx.update_editor(|e, window, cx| {
 6359            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6360        });
 6361        cx.assert_editor_state(expected_state);
 6362    }
 6363
 6364    // Selection starts and ends at the middle of lines, left-to-right
 6365    test(
 6366        &mut cx,
 6367        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6368        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6369    );
 6370    // Same thing, right-to-left
 6371    test(
 6372        &mut cx,
 6373        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6374        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6375    );
 6376
 6377    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6378    test(
 6379        &mut cx,
 6380        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6381        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6382    );
 6383    // Same thing, right-to-left
 6384    test(
 6385        &mut cx,
 6386        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6388    );
 6389
 6390    // Whole buffer, left-to-right, last line ends with newline
 6391    test(
 6392        &mut cx,
 6393        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6394        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6395    );
 6396    // Same thing, right-to-left
 6397    test(
 6398        &mut cx,
 6399        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6400        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6401    );
 6402
 6403    // Starts at the end of a line, ends at the start of another
 6404    test(
 6405        &mut cx,
 6406        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6407        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6408    );
 6409}
 6410
 6411#[gpui::test]
 6412async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6413    init_test(cx, |_| {});
 6414
 6415    let editor = cx.add_window(|window, cx| {
 6416        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6417        build_editor(buffer, window, cx)
 6418    });
 6419
 6420    // setup
 6421    _ = editor.update(cx, |editor, window, cx| {
 6422        editor.fold_creases(
 6423            vec![
 6424                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6425                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6426                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6427            ],
 6428            true,
 6429            window,
 6430            cx,
 6431        );
 6432        assert_eq!(
 6433            editor.display_text(cx),
 6434            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6435        );
 6436    });
 6437
 6438    _ = editor.update(cx, |editor, window, cx| {
 6439        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6440            s.select_display_ranges([
 6441                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6442                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6443                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6444                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6445            ])
 6446        });
 6447        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6448        assert_eq!(
 6449            editor.display_text(cx),
 6450            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6451        );
 6452    });
 6453    EditorTestContext::for_editor(editor, cx)
 6454        .await
 6455        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6456
 6457    _ = editor.update(cx, |editor, window, cx| {
 6458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6459            s.select_display_ranges([
 6460                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6461            ])
 6462        });
 6463        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6464        assert_eq!(
 6465            editor.display_text(cx),
 6466            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6467        );
 6468        assert_eq!(
 6469            editor.selections.display_ranges(cx),
 6470            [
 6471                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6472                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6473                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6474                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6475                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6476                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6477                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6478            ]
 6479        );
 6480    });
 6481    EditorTestContext::for_editor(editor, cx)
 6482        .await
 6483        .assert_editor_state(
 6484            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6485        );
 6486}
 6487
 6488#[gpui::test]
 6489async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6490    init_test(cx, |_| {});
 6491
 6492    let mut cx = EditorTestContext::new(cx).await;
 6493
 6494    cx.set_state(indoc!(
 6495        r#"abc
 6496           defˇghi
 6497
 6498           jk
 6499           nlmo
 6500           "#
 6501    ));
 6502
 6503    cx.update_editor(|editor, window, cx| {
 6504        editor.add_selection_above(&Default::default(), window, cx);
 6505    });
 6506
 6507    cx.assert_editor_state(indoc!(
 6508        r#"abcˇ
 6509           defˇghi
 6510
 6511           jk
 6512           nlmo
 6513           "#
 6514    ));
 6515
 6516    cx.update_editor(|editor, window, cx| {
 6517        editor.add_selection_above(&Default::default(), window, cx);
 6518    });
 6519
 6520    cx.assert_editor_state(indoc!(
 6521        r#"abcˇ
 6522            defˇghi
 6523
 6524            jk
 6525            nlmo
 6526            "#
 6527    ));
 6528
 6529    cx.update_editor(|editor, window, cx| {
 6530        editor.add_selection_below(&Default::default(), window, cx);
 6531    });
 6532
 6533    cx.assert_editor_state(indoc!(
 6534        r#"abc
 6535           defˇghi
 6536
 6537           jk
 6538           nlmo
 6539           "#
 6540    ));
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.undo_selection(&Default::default(), window, cx);
 6544    });
 6545
 6546    cx.assert_editor_state(indoc!(
 6547        r#"abcˇ
 6548           defˇghi
 6549
 6550           jk
 6551           nlmo
 6552           "#
 6553    ));
 6554
 6555    cx.update_editor(|editor, window, cx| {
 6556        editor.redo_selection(&Default::default(), window, cx);
 6557    });
 6558
 6559    cx.assert_editor_state(indoc!(
 6560        r#"abc
 6561           defˇghi
 6562
 6563           jk
 6564           nlmo
 6565           "#
 6566    ));
 6567
 6568    cx.update_editor(|editor, window, cx| {
 6569        editor.add_selection_below(&Default::default(), window, cx);
 6570    });
 6571
 6572    cx.assert_editor_state(indoc!(
 6573        r#"abc
 6574           defˇghi
 6575           ˇ
 6576           jk
 6577           nlmo
 6578           "#
 6579    ));
 6580
 6581    cx.update_editor(|editor, window, cx| {
 6582        editor.add_selection_below(&Default::default(), window, cx);
 6583    });
 6584
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abc
 6587           defˇghi
 6588           ˇ
 6589           jkˇ
 6590           nlmo
 6591           "#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.add_selection_below(&Default::default(), window, cx);
 6596    });
 6597
 6598    cx.assert_editor_state(indoc!(
 6599        r#"abc
 6600           defˇghi
 6601           ˇ
 6602           jkˇ
 6603           nlmˇo
 6604           "#
 6605    ));
 6606
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_below(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"abc
 6613           defˇghi
 6614           ˇ
 6615           jkˇ
 6616           nlmˇo
 6617           ˇ"#
 6618    ));
 6619
 6620    // change selections
 6621    cx.set_state(indoc!(
 6622        r#"abc
 6623           def«ˇg»hi
 6624
 6625           jk
 6626           nlmo
 6627           "#
 6628    ));
 6629
 6630    cx.update_editor(|editor, window, cx| {
 6631        editor.add_selection_below(&Default::default(), window, cx);
 6632    });
 6633
 6634    cx.assert_editor_state(indoc!(
 6635        r#"abc
 6636           def«ˇg»hi
 6637
 6638           jk
 6639           nlm«ˇo»
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"abc
 6649           def«ˇg»hi
 6650
 6651           jk
 6652           nlm«ˇo»
 6653           "#
 6654    ));
 6655
 6656    cx.update_editor(|editor, window, cx| {
 6657        editor.add_selection_above(&Default::default(), window, cx);
 6658    });
 6659
 6660    cx.assert_editor_state(indoc!(
 6661        r#"abc
 6662           def«ˇg»hi
 6663
 6664           jk
 6665           nlmo
 6666           "#
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.add_selection_above(&Default::default(), window, cx);
 6671    });
 6672
 6673    cx.assert_editor_state(indoc!(
 6674        r#"abc
 6675           def«ˇg»hi
 6676
 6677           jk
 6678           nlmo
 6679           "#
 6680    ));
 6681
 6682    // Change selections again
 6683    cx.set_state(indoc!(
 6684        r#"a«bc
 6685           defgˇ»hi
 6686
 6687           jk
 6688           nlmo
 6689           "#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_below(&Default::default(), window, cx);
 6694    });
 6695
 6696    cx.assert_editor_state(indoc!(
 6697        r#"a«bcˇ»
 6698           d«efgˇ»hi
 6699
 6700           j«kˇ»
 6701           nlmo
 6702           "#
 6703    ));
 6704
 6705    cx.update_editor(|editor, window, cx| {
 6706        editor.add_selection_below(&Default::default(), window, cx);
 6707    });
 6708    cx.assert_editor_state(indoc!(
 6709        r#"a«bcˇ»
 6710           d«efgˇ»hi
 6711
 6712           j«kˇ»
 6713           n«lmoˇ»
 6714           "#
 6715    ));
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_above(&Default::default(), window, cx);
 6718    });
 6719
 6720    cx.assert_editor_state(indoc!(
 6721        r#"a«bcˇ»
 6722           d«efgˇ»hi
 6723
 6724           j«kˇ»
 6725           nlmo
 6726           "#
 6727    ));
 6728
 6729    // Change selections again
 6730    cx.set_state(indoc!(
 6731        r#"abc
 6732           d«ˇefghi
 6733
 6734           jk
 6735           nlm»o
 6736           "#
 6737    ));
 6738
 6739    cx.update_editor(|editor, window, cx| {
 6740        editor.add_selection_above(&Default::default(), window, cx);
 6741    });
 6742
 6743    cx.assert_editor_state(indoc!(
 6744        r#"a«ˇbc»
 6745           d«ˇef»ghi
 6746
 6747           j«ˇk»
 6748           n«ˇlm»o
 6749           "#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.add_selection_below(&Default::default(), window, cx);
 6754    });
 6755
 6756    cx.assert_editor_state(indoc!(
 6757        r#"abc
 6758           d«ˇef»ghi
 6759
 6760           j«ˇk»
 6761           n«ˇlm»o
 6762           "#
 6763    ));
 6764}
 6765
 6766#[gpui::test]
 6767async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6768    init_test(cx, |_| {});
 6769    let mut cx = EditorTestContext::new(cx).await;
 6770
 6771    cx.set_state(indoc!(
 6772        r#"line onˇe
 6773           liˇne two
 6774           line three
 6775           line four"#
 6776    ));
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursors expand in the same direction
 6783    cx.assert_editor_state(indoc!(
 6784        r#"line onˇe
 6785           liˇne twˇo
 6786           liˇne three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.add_selection_below(&Default::default(), window, cx);
 6792    });
 6793
 6794    cx.update_editor(|editor, window, cx| {
 6795        editor.add_selection_below(&Default::default(), window, cx);
 6796    });
 6797
 6798    // test multiple cursors expand below overflow
 6799    cx.assert_editor_state(indoc!(
 6800        r#"line onˇe
 6801           liˇne twˇo
 6802           liˇne thˇree
 6803           liˇne foˇur"#
 6804    ));
 6805
 6806    cx.update_editor(|editor, window, cx| {
 6807        editor.add_selection_above(&Default::default(), window, cx);
 6808    });
 6809
 6810    // test multiple cursors retrieves back correctly
 6811    cx.assert_editor_state(indoc!(
 6812        r#"line onˇe
 6813           liˇne twˇo
 6814           liˇne thˇree
 6815           line four"#
 6816    ));
 6817
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.add_selection_above(&Default::default(), window, cx);
 6820    });
 6821
 6822    cx.update_editor(|editor, window, cx| {
 6823        editor.add_selection_above(&Default::default(), window, cx);
 6824    });
 6825
 6826    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6827    cx.assert_editor_state(indoc!(
 6828        r#"liˇne onˇe
 6829           liˇne two
 6830           line three
 6831           line four"#
 6832    ));
 6833
 6834    cx.update_editor(|editor, window, cx| {
 6835        editor.undo_selection(&Default::default(), window, cx);
 6836    });
 6837
 6838    // test undo
 6839    cx.assert_editor_state(indoc!(
 6840        r#"line onˇe
 6841           liˇne twˇo
 6842           line three
 6843           line four"#
 6844    ));
 6845
 6846    cx.update_editor(|editor, window, cx| {
 6847        editor.redo_selection(&Default::default(), window, cx);
 6848    });
 6849
 6850    // test redo
 6851    cx.assert_editor_state(indoc!(
 6852        r#"liˇne onˇe
 6853           liˇne two
 6854           line three
 6855           line four"#
 6856    ));
 6857
 6858    cx.set_state(indoc!(
 6859        r#"abcd
 6860           ef«ghˇ»
 6861           ijkl
 6862           «mˇ»nop"#
 6863    ));
 6864
 6865    cx.update_editor(|editor, window, cx| {
 6866        editor.add_selection_above(&Default::default(), window, cx);
 6867    });
 6868
 6869    // test multiple selections expand in the same direction
 6870    cx.assert_editor_state(indoc!(
 6871        r#"ab«cdˇ»
 6872           ef«ghˇ»
 6873           «iˇ»jkl
 6874           «mˇ»nop"#
 6875    ));
 6876
 6877    cx.update_editor(|editor, window, cx| {
 6878        editor.add_selection_above(&Default::default(), window, cx);
 6879    });
 6880
 6881    // test multiple selection upward overflow
 6882    cx.assert_editor_state(indoc!(
 6883        r#"ab«cdˇ»
 6884           «eˇ»f«ghˇ»
 6885           «iˇ»jkl
 6886           «mˇ»nop"#
 6887    ));
 6888
 6889    cx.update_editor(|editor, window, cx| {
 6890        editor.add_selection_below(&Default::default(), window, cx);
 6891    });
 6892
 6893    // test multiple selection retrieves back correctly
 6894    cx.assert_editor_state(indoc!(
 6895        r#"abcd
 6896           ef«ghˇ»
 6897           «iˇ»jkl
 6898           «mˇ»nop"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_below(&Default::default(), window, cx);
 6903    });
 6904
 6905    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6906    cx.assert_editor_state(indoc!(
 6907        r#"abcd
 6908           ef«ghˇ»
 6909           ij«klˇ»
 6910           «mˇ»nop"#
 6911    ));
 6912
 6913    cx.update_editor(|editor, window, cx| {
 6914        editor.undo_selection(&Default::default(), window, cx);
 6915    });
 6916
 6917    // test undo
 6918    cx.assert_editor_state(indoc!(
 6919        r#"abcd
 6920           ef«ghˇ»
 6921           «iˇ»jkl
 6922           «mˇ»nop"#
 6923    ));
 6924
 6925    cx.update_editor(|editor, window, cx| {
 6926        editor.redo_selection(&Default::default(), window, cx);
 6927    });
 6928
 6929    // test redo
 6930    cx.assert_editor_state(indoc!(
 6931        r#"abcd
 6932           ef«ghˇ»
 6933           ij«klˇ»
 6934           «mˇ»nop"#
 6935    ));
 6936}
 6937
 6938#[gpui::test]
 6939async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6940    init_test(cx, |_| {});
 6941    let mut cx = EditorTestContext::new(cx).await;
 6942
 6943    cx.set_state(indoc!(
 6944        r#"line onˇe
 6945           liˇne two
 6946           line three
 6947           line four"#
 6948    ));
 6949
 6950    cx.update_editor(|editor, window, cx| {
 6951        editor.add_selection_below(&Default::default(), window, cx);
 6952        editor.add_selection_below(&Default::default(), window, cx);
 6953        editor.add_selection_below(&Default::default(), window, cx);
 6954    });
 6955
 6956    // initial state with two multi cursor groups
 6957    cx.assert_editor_state(indoc!(
 6958        r#"line onˇe
 6959           liˇne twˇo
 6960           liˇne thˇree
 6961           liˇne foˇur"#
 6962    ));
 6963
 6964    // add single cursor in middle - simulate opt click
 6965    cx.update_editor(|editor, window, cx| {
 6966        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6967        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6968        editor.end_selection(window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"line onˇe
 6973           liˇne twˇo
 6974           liˇneˇ thˇree
 6975           liˇne foˇur"#
 6976    ));
 6977
 6978    cx.update_editor(|editor, window, cx| {
 6979        editor.add_selection_above(&Default::default(), window, cx);
 6980    });
 6981
 6982    // test new added selection expands above and existing selection shrinks
 6983    cx.assert_editor_state(indoc!(
 6984        r#"line onˇe
 6985           liˇneˇ twˇo
 6986           liˇneˇ thˇree
 6987           line four"#
 6988    ));
 6989
 6990    cx.update_editor(|editor, window, cx| {
 6991        editor.add_selection_above(&Default::default(), window, cx);
 6992    });
 6993
 6994    // test new added selection expands above and existing selection shrinks
 6995    cx.assert_editor_state(indoc!(
 6996        r#"lineˇ onˇe
 6997           liˇneˇ twˇo
 6998           lineˇ three
 6999           line four"#
 7000    ));
 7001
 7002    // intial state with two selection groups
 7003    cx.set_state(indoc!(
 7004        r#"abcd
 7005           ef«ghˇ»
 7006           ijkl
 7007           «mˇ»nop"#
 7008    ));
 7009
 7010    cx.update_editor(|editor, window, cx| {
 7011        editor.add_selection_above(&Default::default(), window, cx);
 7012        editor.add_selection_above(&Default::default(), window, cx);
 7013    });
 7014
 7015    cx.assert_editor_state(indoc!(
 7016        r#"ab«cdˇ»
 7017           «eˇ»f«ghˇ»
 7018           «iˇ»jkl
 7019           «mˇ»nop"#
 7020    ));
 7021
 7022    // add single selection in middle - simulate opt drag
 7023    cx.update_editor(|editor, window, cx| {
 7024        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7025        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7026        editor.update_selection(
 7027            DisplayPoint::new(DisplayRow(2), 4),
 7028            0,
 7029            gpui::Point::<f32>::default(),
 7030            window,
 7031            cx,
 7032        );
 7033        editor.end_selection(window, cx);
 7034    });
 7035
 7036    cx.assert_editor_state(indoc!(
 7037        r#"ab«cdˇ»
 7038           «eˇ»f«ghˇ»
 7039           «iˇ»jk«lˇ»
 7040           «mˇ»nop"#
 7041    ));
 7042
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.add_selection_below(&Default::default(), window, cx);
 7045    });
 7046
 7047    // test new added selection expands below, others shrinks from above
 7048    cx.assert_editor_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           «iˇ»jk«lˇ»
 7052           «mˇ»no«pˇ»"#
 7053    ));
 7054}
 7055
 7056#[gpui::test]
 7057async fn test_select_next(cx: &mut TestAppContext) {
 7058    init_test(cx, |_| {});
 7059
 7060    let mut cx = EditorTestContext::new(cx).await;
 7061    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7062
 7063    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7066
 7067    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7068        .unwrap();
 7069    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7070
 7071    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7072    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7073
 7074    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7075    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7076
 7077    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7078        .unwrap();
 7079    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7080
 7081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7084
 7085    // Test selection direction should be preserved
 7086    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7087
 7088    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7089        .unwrap();
 7090    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7091}
 7092
 7093#[gpui::test]
 7094async fn test_select_all_matches(cx: &mut TestAppContext) {
 7095    init_test(cx, |_| {});
 7096
 7097    let mut cx = EditorTestContext::new(cx).await;
 7098
 7099    // Test caret-only selections
 7100    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7101    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7102        .unwrap();
 7103    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7104
 7105    // Test left-to-right selections
 7106    cx.set_state("abc\n«abcˇ»\nabc");
 7107    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7108        .unwrap();
 7109    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7110
 7111    // Test right-to-left selections
 7112    cx.set_state("abc\n«ˇabc»\nabc");
 7113    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7114        .unwrap();
 7115    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7116
 7117    // Test selecting whitespace with caret selection
 7118    cx.set_state("abc\nˇ   abc\nabc");
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7122
 7123    // Test selecting whitespace with left-to-right selection
 7124    cx.set_state("abc\n«ˇ  »abc\nabc");
 7125    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7126        .unwrap();
 7127    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7128
 7129    // Test no matches with right-to-left selection
 7130    cx.set_state("abc\n«  ˇ»abc\nabc");
 7131    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7132        .unwrap();
 7133    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7134
 7135    // Test with a single word and clip_at_line_ends=true (#29823)
 7136    cx.set_state("aˇbc");
 7137    cx.update_editor(|e, window, cx| {
 7138        e.set_clip_at_line_ends(true, cx);
 7139        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7140        e.set_clip_at_line_ends(false, cx);
 7141    });
 7142    cx.assert_editor_state("«abcˇ»");
 7143}
 7144
 7145#[gpui::test]
 7146async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7147    init_test(cx, |_| {});
 7148
 7149    let mut cx = EditorTestContext::new(cx).await;
 7150
 7151    let large_body_1 = "\nd".repeat(200);
 7152    let large_body_2 = "\ne".repeat(200);
 7153
 7154    cx.set_state(&format!(
 7155        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7156    ));
 7157    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7158        let scroll_position = editor.scroll_position(cx);
 7159        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7160        scroll_position
 7161    });
 7162
 7163    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7164        .unwrap();
 7165    cx.assert_editor_state(&format!(
 7166        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7167    ));
 7168    let scroll_position_after_selection =
 7169        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7170    assert_eq!(
 7171        initial_scroll_position, scroll_position_after_selection,
 7172        "Scroll position should not change after selecting all matches"
 7173    );
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179
 7180    let mut cx = EditorLspTestContext::new_rust(
 7181        lsp::ServerCapabilities {
 7182            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7183            ..Default::default()
 7184        },
 7185        cx,
 7186    )
 7187    .await;
 7188
 7189    cx.set_state(indoc! {"
 7190        line 1
 7191        line 2
 7192        linˇe 3
 7193        line 4
 7194        line 5
 7195    "});
 7196
 7197    // Make an edit
 7198    cx.update_editor(|editor, window, cx| {
 7199        editor.handle_input("X", window, cx);
 7200    });
 7201
 7202    // Move cursor to a different position
 7203    cx.update_editor(|editor, window, cx| {
 7204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7205            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7206        });
 7207    });
 7208
 7209    cx.assert_editor_state(indoc! {"
 7210        line 1
 7211        line 2
 7212        linXe 3
 7213        line 4
 7214        liˇne 5
 7215    "});
 7216
 7217    cx.lsp
 7218        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7219            Ok(Some(vec![lsp::TextEdit::new(
 7220                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7221                "PREFIX ".to_string(),
 7222            )]))
 7223        });
 7224
 7225    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7226        .unwrap()
 7227        .await
 7228        .unwrap();
 7229
 7230    cx.assert_editor_state(indoc! {"
 7231        PREFIX line 1
 7232        line 2
 7233        linXe 3
 7234        line 4
 7235        liˇne 5
 7236    "});
 7237
 7238    // Undo formatting
 7239    cx.update_editor(|editor, window, cx| {
 7240        editor.undo(&Default::default(), window, cx);
 7241    });
 7242
 7243    // Verify cursor moved back to position after edit
 7244    cx.assert_editor_state(indoc! {"
 7245        line 1
 7246        line 2
 7247        linXˇe 3
 7248        line 4
 7249        line 5
 7250    "});
 7251}
 7252
 7253#[gpui::test]
 7254async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7255    init_test(cx, |_| {});
 7256
 7257    let mut cx = EditorTestContext::new(cx).await;
 7258
 7259    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7260    cx.update_editor(|editor, window, cx| {
 7261        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7262    });
 7263
 7264    cx.set_state(indoc! {"
 7265        line 1
 7266        line 2
 7267        linˇe 3
 7268        line 4
 7269        line 5
 7270        line 6
 7271        line 7
 7272        line 8
 7273        line 9
 7274        line 10
 7275    "});
 7276
 7277    let snapshot = cx.buffer_snapshot();
 7278    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7279
 7280    cx.update(|_, cx| {
 7281        provider.update(cx, |provider, _| {
 7282            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7283                id: None,
 7284                edits: vec![(edit_position..edit_position, "X".into())],
 7285                edit_preview: None,
 7286            }))
 7287        })
 7288    });
 7289
 7290    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7291    cx.update_editor(|editor, window, cx| {
 7292        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7293    });
 7294
 7295    cx.assert_editor_state(indoc! {"
 7296        line 1
 7297        line 2
 7298        lineXˇ 3
 7299        line 4
 7300        line 5
 7301        line 6
 7302        line 7
 7303        line 8
 7304        line 9
 7305        line 10
 7306    "});
 7307
 7308    cx.update_editor(|editor, window, cx| {
 7309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7310            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7311        });
 7312    });
 7313
 7314    cx.assert_editor_state(indoc! {"
 7315        line 1
 7316        line 2
 7317        lineX 3
 7318        line 4
 7319        line 5
 7320        line 6
 7321        line 7
 7322        line 8
 7323        line 9
 7324        liˇne 10
 7325    "});
 7326
 7327    cx.update_editor(|editor, window, cx| {
 7328        editor.undo(&Default::default(), window, cx);
 7329    });
 7330
 7331    cx.assert_editor_state(indoc! {"
 7332        line 1
 7333        line 2
 7334        lineˇ 3
 7335        line 4
 7336        line 5
 7337        line 6
 7338        line 7
 7339        line 8
 7340        line 9
 7341        line 10
 7342    "});
 7343}
 7344
 7345#[gpui::test]
 7346async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7347    init_test(cx, |_| {});
 7348
 7349    let mut cx = EditorTestContext::new(cx).await;
 7350    cx.set_state(
 7351        r#"let foo = 2;
 7352lˇet foo = 2;
 7353let fooˇ = 2;
 7354let foo = 2;
 7355let foo = ˇ2;"#,
 7356    );
 7357
 7358    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7359        .unwrap();
 7360    cx.assert_editor_state(
 7361        r#"let foo = 2;
 7362«letˇ» foo = 2;
 7363let «fooˇ» = 2;
 7364let foo = 2;
 7365let foo = «2ˇ»;"#,
 7366    );
 7367
 7368    // noop for multiple selections with different contents
 7369    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7370        .unwrap();
 7371    cx.assert_editor_state(
 7372        r#"let foo = 2;
 7373«letˇ» foo = 2;
 7374let «fooˇ» = 2;
 7375let foo = 2;
 7376let foo = «2ˇ»;"#,
 7377    );
 7378
 7379    // Test last selection direction should be preserved
 7380    cx.set_state(
 7381        r#"let foo = 2;
 7382let foo = 2;
 7383let «fooˇ» = 2;
 7384let «ˇfoo» = 2;
 7385let foo = 2;"#,
 7386    );
 7387
 7388    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7389        .unwrap();
 7390    cx.assert_editor_state(
 7391        r#"let foo = 2;
 7392let foo = 2;
 7393let «fooˇ» = 2;
 7394let «ˇfoo» = 2;
 7395let «ˇfoo» = 2;"#,
 7396    );
 7397}
 7398
 7399#[gpui::test]
 7400async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7401    init_test(cx, |_| {});
 7402
 7403    let mut cx =
 7404        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7405
 7406    cx.assert_editor_state(indoc! {"
 7407        ˇbbb
 7408        ccc
 7409
 7410        bbb
 7411        ccc
 7412        "});
 7413    cx.dispatch_action(SelectPrevious::default());
 7414    cx.assert_editor_state(indoc! {"
 7415                «bbbˇ»
 7416                ccc
 7417
 7418                bbb
 7419                ccc
 7420                "});
 7421    cx.dispatch_action(SelectPrevious::default());
 7422    cx.assert_editor_state(indoc! {"
 7423                «bbbˇ»
 7424                ccc
 7425
 7426                «bbbˇ»
 7427                ccc
 7428                "});
 7429}
 7430
 7431#[gpui::test]
 7432async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7433    init_test(cx, |_| {});
 7434
 7435    let mut cx = EditorTestContext::new(cx).await;
 7436    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7437
 7438    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7439        .unwrap();
 7440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7441
 7442    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7443        .unwrap();
 7444    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7445
 7446    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7447    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7448
 7449    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7450    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7451
 7452    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7453        .unwrap();
 7454    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7455
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7459}
 7460
 7461#[gpui::test]
 7462async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7463    init_test(cx, |_| {});
 7464
 7465    let mut cx = EditorTestContext::new(cx).await;
 7466    cx.set_state("");
 7467
 7468    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7469        .unwrap();
 7470    cx.assert_editor_state("«aˇ»");
 7471    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7472        .unwrap();
 7473    cx.assert_editor_state("«aˇ»");
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state(
 7482        r#"let foo = 2;
 7483lˇet foo = 2;
 7484let fooˇ = 2;
 7485let foo = 2;
 7486let foo = ˇ2;"#,
 7487    );
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state(
 7492        r#"let foo = 2;
 7493«letˇ» foo = 2;
 7494let «fooˇ» = 2;
 7495let foo = 2;
 7496let foo = «2ˇ»;"#,
 7497    );
 7498
 7499    // noop for multiple selections with different contents
 7500    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7501        .unwrap();
 7502    cx.assert_editor_state(
 7503        r#"let foo = 2;
 7504«letˇ» foo = 2;
 7505let «fooˇ» = 2;
 7506let foo = 2;
 7507let foo = «2ˇ»;"#,
 7508    );
 7509}
 7510
 7511#[gpui::test]
 7512async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7513    init_test(cx, |_| {});
 7514
 7515    let mut cx = EditorTestContext::new(cx).await;
 7516    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7517
 7518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7519        .unwrap();
 7520    // selection direction is preserved
 7521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7522
 7523    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7524        .unwrap();
 7525    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7526
 7527    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7529
 7530    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7531    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7532
 7533    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7534        .unwrap();
 7535    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7536
 7537    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7538        .unwrap();
 7539    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7540}
 7541
 7542#[gpui::test]
 7543async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7544    init_test(cx, |_| {});
 7545
 7546    let language = Arc::new(Language::new(
 7547        LanguageConfig::default(),
 7548        Some(tree_sitter_rust::LANGUAGE.into()),
 7549    ));
 7550
 7551    let text = r#"
 7552        use mod1::mod2::{mod3, mod4};
 7553
 7554        fn fn_1(param1: bool, param2: &str) {
 7555            let var1 = "text";
 7556        }
 7557    "#
 7558    .unindent();
 7559
 7560    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7561    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7562    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7563
 7564    editor
 7565        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7566        .await;
 7567
 7568    editor.update_in(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7572                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7573                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7574            ]);
 7575        });
 7576        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7577    });
 7578    editor.update(cx, |editor, cx| {
 7579        assert_text_with_selections(
 7580            editor,
 7581            indoc! {r#"
 7582                use mod1::mod2::{mod3, «mod4ˇ»};
 7583
 7584                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7585                    let var1 = "«ˇtext»";
 7586                }
 7587            "#},
 7588            cx,
 7589        );
 7590    });
 7591
 7592    editor.update_in(cx, |editor, window, cx| {
 7593        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7594    });
 7595    editor.update(cx, |editor, cx| {
 7596        assert_text_with_selections(
 7597            editor,
 7598            indoc! {r#"
 7599                use mod1::mod2::«{mod3, mod4}ˇ»;
 7600
 7601                «ˇfn fn_1(param1: bool, param2: &str) {
 7602                    let var1 = "text";
 7603 7604            "#},
 7605            cx,
 7606        );
 7607    });
 7608
 7609    editor.update_in(cx, |editor, window, cx| {
 7610        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7611    });
 7612    assert_eq!(
 7613        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7614        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7615    );
 7616
 7617    // Trying to expand the selected syntax node one more time has no effect.
 7618    editor.update_in(cx, |editor, window, cx| {
 7619        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7620    });
 7621    assert_eq!(
 7622        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7623        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7624    );
 7625
 7626    editor.update_in(cx, |editor, window, cx| {
 7627        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7628    });
 7629    editor.update(cx, |editor, cx| {
 7630        assert_text_with_selections(
 7631            editor,
 7632            indoc! {r#"
 7633                use mod1::mod2::«{mod3, mod4}ˇ»;
 7634
 7635                «ˇfn fn_1(param1: bool, param2: &str) {
 7636                    let var1 = "text";
 7637 7638            "#},
 7639            cx,
 7640        );
 7641    });
 7642
 7643    editor.update_in(cx, |editor, window, cx| {
 7644        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7645    });
 7646    editor.update(cx, |editor, cx| {
 7647        assert_text_with_selections(
 7648            editor,
 7649            indoc! {r#"
 7650                use mod1::mod2::{mod3, «mod4ˇ»};
 7651
 7652                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7653                    let var1 = "«ˇtext»";
 7654                }
 7655            "#},
 7656            cx,
 7657        );
 7658    });
 7659
 7660    editor.update_in(cx, |editor, window, cx| {
 7661        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7662    });
 7663    editor.update(cx, |editor, cx| {
 7664        assert_text_with_selections(
 7665            editor,
 7666            indoc! {r#"
 7667                use mod1::mod2::{mod3, mo«ˇ»d4};
 7668
 7669                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7670                    let var1 = "te«ˇ»xt";
 7671                }
 7672            "#},
 7673            cx,
 7674        );
 7675    });
 7676
 7677    // Trying to shrink the selected syntax node one more time has no effect.
 7678    editor.update_in(cx, |editor, window, cx| {
 7679        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7680    });
 7681    editor.update_in(cx, |editor, _, cx| {
 7682        assert_text_with_selections(
 7683            editor,
 7684            indoc! {r#"
 7685                use mod1::mod2::{mod3, mo«ˇ»d4};
 7686
 7687                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7688                    let var1 = "te«ˇ»xt";
 7689                }
 7690            "#},
 7691            cx,
 7692        );
 7693    });
 7694
 7695    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7696    // a fold.
 7697    editor.update_in(cx, |editor, window, cx| {
 7698        editor.fold_creases(
 7699            vec![
 7700                Crease::simple(
 7701                    Point::new(0, 21)..Point::new(0, 24),
 7702                    FoldPlaceholder::test(),
 7703                ),
 7704                Crease::simple(
 7705                    Point::new(3, 20)..Point::new(3, 22),
 7706                    FoldPlaceholder::test(),
 7707                ),
 7708            ],
 7709            true,
 7710            window,
 7711            cx,
 7712        );
 7713        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7714    });
 7715    editor.update(cx, |editor, cx| {
 7716        assert_text_with_selections(
 7717            editor,
 7718            indoc! {r#"
 7719                use mod1::mod2::«{mod3, mod4}ˇ»;
 7720
 7721                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7722                    let var1 = "«ˇtext»";
 7723                }
 7724            "#},
 7725            cx,
 7726        );
 7727    });
 7728}
 7729
 7730#[gpui::test]
 7731async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7732    init_test(cx, |_| {});
 7733
 7734    let language = Arc::new(Language::new(
 7735        LanguageConfig::default(),
 7736        Some(tree_sitter_rust::LANGUAGE.into()),
 7737    ));
 7738
 7739    let text = "let a = 2;";
 7740
 7741    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7742    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7743    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7744
 7745    editor
 7746        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7747        .await;
 7748
 7749    // Test case 1: Cursor at end of word
 7750    editor.update_in(cx, |editor, window, cx| {
 7751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7752            s.select_display_ranges([
 7753                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7754            ]);
 7755        });
 7756    });
 7757    editor.update(cx, |editor, cx| {
 7758        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7759    });
 7760    editor.update_in(cx, |editor, window, cx| {
 7761        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7762    });
 7763    editor.update(cx, |editor, cx| {
 7764        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7765    });
 7766    editor.update_in(cx, |editor, window, cx| {
 7767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7768    });
 7769    editor.update(cx, |editor, cx| {
 7770        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7771    });
 7772
 7773    // Test case 2: Cursor at end of statement
 7774    editor.update_in(cx, |editor, window, cx| {
 7775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7776            s.select_display_ranges([
 7777                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7778            ]);
 7779        });
 7780    });
 7781    editor.update(cx, |editor, cx| {
 7782        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7783    });
 7784    editor.update_in(cx, |editor, window, cx| {
 7785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7786    });
 7787    editor.update(cx, |editor, cx| {
 7788        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7789    });
 7790}
 7791
 7792#[gpui::test]
 7793async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7794    init_test(cx, |_| {});
 7795
 7796    let language = Arc::new(Language::new(
 7797        LanguageConfig::default(),
 7798        Some(tree_sitter_rust::LANGUAGE.into()),
 7799    ));
 7800
 7801    let text = r#"
 7802        use mod1::mod2::{mod3, mod4};
 7803
 7804        fn fn_1(param1: bool, param2: &str) {
 7805            let var1 = "hello world";
 7806        }
 7807    "#
 7808    .unindent();
 7809
 7810    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7811    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7812    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7813
 7814    editor
 7815        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7816        .await;
 7817
 7818    // Test 1: Cursor on a letter of a string word
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update_in(cx, |editor, window, cx| {
 7827        assert_text_with_selections(
 7828            editor,
 7829            indoc! {r#"
 7830                use mod1::mod2::{mod3, mod4};
 7831
 7832                fn fn_1(param1: bool, param2: &str) {
 7833                    let var1 = "hˇello world";
 7834                }
 7835            "#},
 7836            cx,
 7837        );
 7838        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7839        assert_text_with_selections(
 7840            editor,
 7841            indoc! {r#"
 7842                use mod1::mod2::{mod3, mod4};
 7843
 7844                fn fn_1(param1: bool, param2: &str) {
 7845                    let var1 = "«ˇhello» world";
 7846                }
 7847            "#},
 7848            cx,
 7849        );
 7850    });
 7851
 7852    // Test 2: Partial selection within a word
 7853    editor.update_in(cx, |editor, window, cx| {
 7854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7855            s.select_display_ranges([
 7856                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7857            ]);
 7858        });
 7859    });
 7860    editor.update_in(cx, |editor, window, cx| {
 7861        assert_text_with_selections(
 7862            editor,
 7863            indoc! {r#"
 7864                use mod1::mod2::{mod3, mod4};
 7865
 7866                fn fn_1(param1: bool, param2: &str) {
 7867                    let var1 = "h«elˇ»lo world";
 7868                }
 7869            "#},
 7870            cx,
 7871        );
 7872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7873        assert_text_with_selections(
 7874            editor,
 7875            indoc! {r#"
 7876                use mod1::mod2::{mod3, mod4};
 7877
 7878                fn fn_1(param1: bool, param2: &str) {
 7879                    let var1 = "«ˇhello» world";
 7880                }
 7881            "#},
 7882            cx,
 7883        );
 7884    });
 7885
 7886    // Test 3: Complete word already selected
 7887    editor.update_in(cx, |editor, window, cx| {
 7888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7889            s.select_display_ranges([
 7890                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7891            ]);
 7892        });
 7893    });
 7894    editor.update_in(cx, |editor, window, cx| {
 7895        assert_text_with_selections(
 7896            editor,
 7897            indoc! {r#"
 7898                use mod1::mod2::{mod3, mod4};
 7899
 7900                fn fn_1(param1: bool, param2: &str) {
 7901                    let var1 = "«helloˇ» world";
 7902                }
 7903            "#},
 7904            cx,
 7905        );
 7906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7907        assert_text_with_selections(
 7908            editor,
 7909            indoc! {r#"
 7910                use mod1::mod2::{mod3, mod4};
 7911
 7912                fn fn_1(param1: bool, param2: &str) {
 7913                    let var1 = "«hello worldˇ»";
 7914                }
 7915            "#},
 7916            cx,
 7917        );
 7918    });
 7919
 7920    // Test 4: Selection spanning across words
 7921    editor.update_in(cx, |editor, window, cx| {
 7922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7923            s.select_display_ranges([
 7924                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7925            ]);
 7926        });
 7927    });
 7928    editor.update_in(cx, |editor, window, cx| {
 7929        assert_text_with_selections(
 7930            editor,
 7931            indoc! {r#"
 7932                use mod1::mod2::{mod3, mod4};
 7933
 7934                fn fn_1(param1: bool, param2: &str) {
 7935                    let var1 = "hel«lo woˇ»rld";
 7936                }
 7937            "#},
 7938            cx,
 7939        );
 7940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7941        assert_text_with_selections(
 7942            editor,
 7943            indoc! {r#"
 7944                use mod1::mod2::{mod3, mod4};
 7945
 7946                fn fn_1(param1: bool, param2: &str) {
 7947                    let var1 = "«ˇhello world»";
 7948                }
 7949            "#},
 7950            cx,
 7951        );
 7952    });
 7953
 7954    // Test 5: Expansion beyond string
 7955    editor.update_in(cx, |editor, window, cx| {
 7956        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7957        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7958        assert_text_with_selections(
 7959            editor,
 7960            indoc! {r#"
 7961                use mod1::mod2::{mod3, mod4};
 7962
 7963                fn fn_1(param1: bool, param2: &str) {
 7964                    «ˇlet var1 = "hello world";»
 7965                }
 7966            "#},
 7967            cx,
 7968        );
 7969    });
 7970}
 7971
 7972#[gpui::test]
 7973async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7974    init_test(cx, |_| {});
 7975
 7976    let base_text = r#"
 7977        impl A {
 7978            // this is an uncommitted comment
 7979
 7980            fn b() {
 7981                c();
 7982            }
 7983
 7984            // this is another uncommitted comment
 7985
 7986            fn d() {
 7987                // e
 7988                // f
 7989            }
 7990        }
 7991
 7992        fn g() {
 7993            // h
 7994        }
 7995    "#
 7996    .unindent();
 7997
 7998    let text = r#"
 7999        ˇimpl A {
 8000
 8001            fn b() {
 8002                c();
 8003            }
 8004
 8005            fn d() {
 8006                // e
 8007                // f
 8008            }
 8009        }
 8010
 8011        fn g() {
 8012            // h
 8013        }
 8014    "#
 8015    .unindent();
 8016
 8017    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8018    cx.set_state(&text);
 8019    cx.set_head_text(&base_text);
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8022    });
 8023
 8024    cx.assert_state_with_diff(
 8025        "
 8026        ˇimpl A {
 8027      -     // this is an uncommitted comment
 8028
 8029            fn b() {
 8030                c();
 8031            }
 8032
 8033      -     // this is another uncommitted comment
 8034      -
 8035            fn d() {
 8036                // e
 8037                // f
 8038            }
 8039        }
 8040
 8041        fn g() {
 8042            // h
 8043        }
 8044    "
 8045        .unindent(),
 8046    );
 8047
 8048    let expected_display_text = "
 8049        impl A {
 8050            // this is an uncommitted comment
 8051
 8052            fn b() {
 8053 8054            }
 8055
 8056            // this is another uncommitted comment
 8057
 8058            fn d() {
 8059 8060            }
 8061        }
 8062
 8063        fn g() {
 8064 8065        }
 8066        "
 8067    .unindent();
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8071        assert_eq!(editor.display_text(cx), expected_display_text);
 8072    });
 8073}
 8074
 8075#[gpui::test]
 8076async fn test_autoindent(cx: &mut TestAppContext) {
 8077    init_test(cx, |_| {});
 8078
 8079    let language = Arc::new(
 8080        Language::new(
 8081            LanguageConfig {
 8082                brackets: BracketPairConfig {
 8083                    pairs: vec![
 8084                        BracketPair {
 8085                            start: "{".to_string(),
 8086                            end: "}".to_string(),
 8087                            close: false,
 8088                            surround: false,
 8089                            newline: true,
 8090                        },
 8091                        BracketPair {
 8092                            start: "(".to_string(),
 8093                            end: ")".to_string(),
 8094                            close: false,
 8095                            surround: false,
 8096                            newline: true,
 8097                        },
 8098                    ],
 8099                    ..Default::default()
 8100                },
 8101                ..Default::default()
 8102            },
 8103            Some(tree_sitter_rust::LANGUAGE.into()),
 8104        )
 8105        .with_indents_query(
 8106            r#"
 8107                (_ "(" ")" @end) @indent
 8108                (_ "{" "}" @end) @indent
 8109            "#,
 8110        )
 8111        .unwrap(),
 8112    );
 8113
 8114    let text = "fn a() {}";
 8115
 8116    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8117    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8118    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8119    editor
 8120        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8121        .await;
 8122
 8123    editor.update_in(cx, |editor, window, cx| {
 8124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8125            s.select_ranges([5..5, 8..8, 9..9])
 8126        });
 8127        editor.newline(&Newline, window, cx);
 8128        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8129        assert_eq!(
 8130            editor.selections.ranges(cx),
 8131            &[
 8132                Point::new(1, 4)..Point::new(1, 4),
 8133                Point::new(3, 4)..Point::new(3, 4),
 8134                Point::new(5, 0)..Point::new(5, 0)
 8135            ]
 8136        );
 8137    });
 8138}
 8139
 8140#[gpui::test]
 8141async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8142    init_test(cx, |_| {});
 8143
 8144    {
 8145        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8146        cx.set_state(indoc! {"
 8147            impl A {
 8148
 8149                fn b() {}
 8150
 8151            «fn c() {
 8152
 8153            }ˇ»
 8154            }
 8155        "});
 8156
 8157        cx.update_editor(|editor, window, cx| {
 8158            editor.autoindent(&Default::default(), window, cx);
 8159        });
 8160
 8161        cx.assert_editor_state(indoc! {"
 8162            impl A {
 8163
 8164                fn b() {}
 8165
 8166                «fn c() {
 8167
 8168                }ˇ»
 8169            }
 8170        "});
 8171    }
 8172
 8173    {
 8174        let mut cx = EditorTestContext::new_multibuffer(
 8175            cx,
 8176            [indoc! { "
 8177                impl A {
 8178                «
 8179                // a
 8180                fn b(){}
 8181                »
 8182                «
 8183                    }
 8184                    fn c(){}
 8185                »
 8186            "}],
 8187        );
 8188
 8189        let buffer = cx.update_editor(|editor, _, cx| {
 8190            let buffer = editor.buffer().update(cx, |buffer, _| {
 8191                buffer.all_buffers().iter().next().unwrap().clone()
 8192            });
 8193            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8194            buffer
 8195        });
 8196
 8197        cx.run_until_parked();
 8198        cx.update_editor(|editor, window, cx| {
 8199            editor.select_all(&Default::default(), window, cx);
 8200            editor.autoindent(&Default::default(), window, cx)
 8201        });
 8202        cx.run_until_parked();
 8203
 8204        cx.update(|_, cx| {
 8205            assert_eq!(
 8206                buffer.read(cx).text(),
 8207                indoc! { "
 8208                    impl A {
 8209
 8210                        // a
 8211                        fn b(){}
 8212
 8213
 8214                    }
 8215                    fn c(){}
 8216
 8217                " }
 8218            )
 8219        });
 8220    }
 8221}
 8222
 8223#[gpui::test]
 8224async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8225    init_test(cx, |_| {});
 8226
 8227    let mut cx = EditorTestContext::new(cx).await;
 8228
 8229    let language = Arc::new(Language::new(
 8230        LanguageConfig {
 8231            brackets: BracketPairConfig {
 8232                pairs: vec![
 8233                    BracketPair {
 8234                        start: "{".to_string(),
 8235                        end: "}".to_string(),
 8236                        close: true,
 8237                        surround: true,
 8238                        newline: true,
 8239                    },
 8240                    BracketPair {
 8241                        start: "(".to_string(),
 8242                        end: ")".to_string(),
 8243                        close: true,
 8244                        surround: true,
 8245                        newline: true,
 8246                    },
 8247                    BracketPair {
 8248                        start: "/*".to_string(),
 8249                        end: " */".to_string(),
 8250                        close: true,
 8251                        surround: true,
 8252                        newline: true,
 8253                    },
 8254                    BracketPair {
 8255                        start: "[".to_string(),
 8256                        end: "]".to_string(),
 8257                        close: false,
 8258                        surround: false,
 8259                        newline: true,
 8260                    },
 8261                    BracketPair {
 8262                        start: "\"".to_string(),
 8263                        end: "\"".to_string(),
 8264                        close: true,
 8265                        surround: true,
 8266                        newline: false,
 8267                    },
 8268                    BracketPair {
 8269                        start: "<".to_string(),
 8270                        end: ">".to_string(),
 8271                        close: false,
 8272                        surround: true,
 8273                        newline: true,
 8274                    },
 8275                ],
 8276                ..Default::default()
 8277            },
 8278            autoclose_before: "})]".to_string(),
 8279            ..Default::default()
 8280        },
 8281        Some(tree_sitter_rust::LANGUAGE.into()),
 8282    ));
 8283
 8284    cx.language_registry().add(language.clone());
 8285    cx.update_buffer(|buffer, cx| {
 8286        buffer.set_language(Some(language), cx);
 8287    });
 8288
 8289    cx.set_state(
 8290        &r#"
 8291            🏀ˇ
 8292            εˇ
 8293            ❤️ˇ
 8294        "#
 8295        .unindent(),
 8296    );
 8297
 8298    // autoclose multiple nested brackets at multiple cursors
 8299    cx.update_editor(|editor, window, cx| {
 8300        editor.handle_input("{", window, cx);
 8301        editor.handle_input("{", window, cx);
 8302        editor.handle_input("{", window, cx);
 8303    });
 8304    cx.assert_editor_state(
 8305        &"
 8306            🏀{{{ˇ}}}
 8307            ε{{{ˇ}}}
 8308            ❤️{{{ˇ}}}
 8309        "
 8310        .unindent(),
 8311    );
 8312
 8313    // insert a different closing bracket
 8314    cx.update_editor(|editor, window, cx| {
 8315        editor.handle_input(")", window, cx);
 8316    });
 8317    cx.assert_editor_state(
 8318        &"
 8319            🏀{{{)ˇ}}}
 8320            ε{{{)ˇ}}}
 8321            ❤️{{{)ˇ}}}
 8322        "
 8323        .unindent(),
 8324    );
 8325
 8326    // skip over the auto-closed brackets when typing a closing bracket
 8327    cx.update_editor(|editor, window, cx| {
 8328        editor.move_right(&MoveRight, window, cx);
 8329        editor.handle_input("}", window, cx);
 8330        editor.handle_input("}", window, cx);
 8331        editor.handle_input("}", window, cx);
 8332    });
 8333    cx.assert_editor_state(
 8334        &"
 8335            🏀{{{)}}}}ˇ
 8336            ε{{{)}}}}ˇ
 8337            ❤️{{{)}}}}ˇ
 8338        "
 8339        .unindent(),
 8340    );
 8341
 8342    // autoclose multi-character pairs
 8343    cx.set_state(
 8344        &"
 8345            ˇ
 8346            ˇ
 8347        "
 8348        .unindent(),
 8349    );
 8350    cx.update_editor(|editor, window, cx| {
 8351        editor.handle_input("/", window, cx);
 8352        editor.handle_input("*", window, cx);
 8353    });
 8354    cx.assert_editor_state(
 8355        &"
 8356            /*ˇ */
 8357            /*ˇ */
 8358        "
 8359        .unindent(),
 8360    );
 8361
 8362    // one cursor autocloses a multi-character pair, one cursor
 8363    // does not autoclose.
 8364    cx.set_state(
 8365        &"
 8366 8367            ˇ
 8368        "
 8369        .unindent(),
 8370    );
 8371    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8372    cx.assert_editor_state(
 8373        &"
 8374            /*ˇ */
 8375 8376        "
 8377        .unindent(),
 8378    );
 8379
 8380    // Don't autoclose if the next character isn't whitespace and isn't
 8381    // listed in the language's "autoclose_before" section.
 8382    cx.set_state("ˇa b");
 8383    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8384    cx.assert_editor_state("{ˇa b");
 8385
 8386    // Don't autoclose if `close` is false for the bracket pair
 8387    cx.set_state("ˇ");
 8388    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8389    cx.assert_editor_state("");
 8390
 8391    // Surround with brackets if text is selected
 8392    cx.set_state("«aˇ» b");
 8393    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8394    cx.assert_editor_state("{«aˇ»} b");
 8395
 8396    // Autoclose when not immediately after a word character
 8397    cx.set_state("a ˇ");
 8398    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8399    cx.assert_editor_state("a \"ˇ\"");
 8400
 8401    // Autoclose pair where the start and end characters are the same
 8402    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8403    cx.assert_editor_state("a \"\"ˇ");
 8404
 8405    // Don't autoclose when immediately after a word character
 8406    cx.set_state("");
 8407    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8408    cx.assert_editor_state("a\"ˇ");
 8409
 8410    // Do autoclose when after a non-word character
 8411    cx.set_state("");
 8412    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8413    cx.assert_editor_state("{\"ˇ\"");
 8414
 8415    // Non identical pairs autoclose regardless of preceding character
 8416    cx.set_state("");
 8417    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8418    cx.assert_editor_state("a{ˇ}");
 8419
 8420    // Don't autoclose pair if autoclose is disabled
 8421    cx.set_state("ˇ");
 8422    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8423    cx.assert_editor_state("");
 8424
 8425    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8426    cx.set_state("«aˇ» b");
 8427    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8428    cx.assert_editor_state("<«aˇ»> b");
 8429}
 8430
 8431#[gpui::test]
 8432async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8433    init_test(cx, |settings| {
 8434        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8435    });
 8436
 8437    let mut cx = EditorTestContext::new(cx).await;
 8438
 8439    let language = Arc::new(Language::new(
 8440        LanguageConfig {
 8441            brackets: BracketPairConfig {
 8442                pairs: vec![
 8443                    BracketPair {
 8444                        start: "{".to_string(),
 8445                        end: "}".to_string(),
 8446                        close: true,
 8447                        surround: true,
 8448                        newline: true,
 8449                    },
 8450                    BracketPair {
 8451                        start: "(".to_string(),
 8452                        end: ")".to_string(),
 8453                        close: true,
 8454                        surround: true,
 8455                        newline: true,
 8456                    },
 8457                    BracketPair {
 8458                        start: "[".to_string(),
 8459                        end: "]".to_string(),
 8460                        close: false,
 8461                        surround: false,
 8462                        newline: true,
 8463                    },
 8464                ],
 8465                ..Default::default()
 8466            },
 8467            autoclose_before: "})]".to_string(),
 8468            ..Default::default()
 8469        },
 8470        Some(tree_sitter_rust::LANGUAGE.into()),
 8471    ));
 8472
 8473    cx.language_registry().add(language.clone());
 8474    cx.update_buffer(|buffer, cx| {
 8475        buffer.set_language(Some(language), cx);
 8476    });
 8477
 8478    cx.set_state(
 8479        &"
 8480            ˇ
 8481            ˇ
 8482            ˇ
 8483        "
 8484        .unindent(),
 8485    );
 8486
 8487    // ensure only matching closing brackets are skipped over
 8488    cx.update_editor(|editor, window, cx| {
 8489        editor.handle_input("}", window, cx);
 8490        editor.move_left(&MoveLeft, window, cx);
 8491        editor.handle_input(")", window, cx);
 8492        editor.move_left(&MoveLeft, window, cx);
 8493    });
 8494    cx.assert_editor_state(
 8495        &"
 8496            ˇ)}
 8497            ˇ)}
 8498            ˇ)}
 8499        "
 8500        .unindent(),
 8501    );
 8502
 8503    // skip-over closing brackets at multiple cursors
 8504    cx.update_editor(|editor, window, cx| {
 8505        editor.handle_input(")", window, cx);
 8506        editor.handle_input("}", window, cx);
 8507    });
 8508    cx.assert_editor_state(
 8509        &"
 8510            )}ˇ
 8511            )}ˇ
 8512            )}ˇ
 8513        "
 8514        .unindent(),
 8515    );
 8516
 8517    // ignore non-close brackets
 8518    cx.update_editor(|editor, window, cx| {
 8519        editor.handle_input("]", window, cx);
 8520        editor.move_left(&MoveLeft, window, cx);
 8521        editor.handle_input("]", window, cx);
 8522    });
 8523    cx.assert_editor_state(
 8524        &"
 8525            )}]ˇ]
 8526            )}]ˇ]
 8527            )}]ˇ]
 8528        "
 8529        .unindent(),
 8530    );
 8531}
 8532
 8533#[gpui::test]
 8534async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8535    init_test(cx, |_| {});
 8536
 8537    let mut cx = EditorTestContext::new(cx).await;
 8538
 8539    let html_language = Arc::new(
 8540        Language::new(
 8541            LanguageConfig {
 8542                name: "HTML".into(),
 8543                brackets: BracketPairConfig {
 8544                    pairs: vec![
 8545                        BracketPair {
 8546                            start: "<".into(),
 8547                            end: ">".into(),
 8548                            close: true,
 8549                            ..Default::default()
 8550                        },
 8551                        BracketPair {
 8552                            start: "{".into(),
 8553                            end: "}".into(),
 8554                            close: true,
 8555                            ..Default::default()
 8556                        },
 8557                        BracketPair {
 8558                            start: "(".into(),
 8559                            end: ")".into(),
 8560                            close: true,
 8561                            ..Default::default()
 8562                        },
 8563                    ],
 8564                    ..Default::default()
 8565                },
 8566                autoclose_before: "})]>".into(),
 8567                ..Default::default()
 8568            },
 8569            Some(tree_sitter_html::LANGUAGE.into()),
 8570        )
 8571        .with_injection_query(
 8572            r#"
 8573            (script_element
 8574                (raw_text) @injection.content
 8575                (#set! injection.language "javascript"))
 8576            "#,
 8577        )
 8578        .unwrap(),
 8579    );
 8580
 8581    let javascript_language = Arc::new(Language::new(
 8582        LanguageConfig {
 8583            name: "JavaScript".into(),
 8584            brackets: BracketPairConfig {
 8585                pairs: vec![
 8586                    BracketPair {
 8587                        start: "/*".into(),
 8588                        end: " */".into(),
 8589                        close: true,
 8590                        ..Default::default()
 8591                    },
 8592                    BracketPair {
 8593                        start: "{".into(),
 8594                        end: "}".into(),
 8595                        close: true,
 8596                        ..Default::default()
 8597                    },
 8598                    BracketPair {
 8599                        start: "(".into(),
 8600                        end: ")".into(),
 8601                        close: true,
 8602                        ..Default::default()
 8603                    },
 8604                ],
 8605                ..Default::default()
 8606            },
 8607            autoclose_before: "})]>".into(),
 8608            ..Default::default()
 8609        },
 8610        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8611    ));
 8612
 8613    cx.language_registry().add(html_language.clone());
 8614    cx.language_registry().add(javascript_language.clone());
 8615
 8616    cx.update_buffer(|buffer, cx| {
 8617        buffer.set_language(Some(html_language), cx);
 8618    });
 8619
 8620    cx.set_state(
 8621        &r#"
 8622            <body>ˇ
 8623                <script>
 8624                    var x = 1;ˇ
 8625                </script>
 8626            </body>ˇ
 8627        "#
 8628        .unindent(),
 8629    );
 8630
 8631    // Precondition: different languages are active at different locations.
 8632    cx.update_editor(|editor, window, cx| {
 8633        let snapshot = editor.snapshot(window, cx);
 8634        let cursors = editor.selections.ranges::<usize>(cx);
 8635        let languages = cursors
 8636            .iter()
 8637            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8638            .collect::<Vec<_>>();
 8639        assert_eq!(
 8640            languages,
 8641            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8642        );
 8643    });
 8644
 8645    // Angle brackets autoclose in HTML, but not JavaScript.
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.handle_input("<", window, cx);
 8648        editor.handle_input("a", window, cx);
 8649    });
 8650    cx.assert_editor_state(
 8651        &r#"
 8652            <body><aˇ>
 8653                <script>
 8654                    var x = 1;<aˇ
 8655                </script>
 8656            </body><aˇ>
 8657        "#
 8658        .unindent(),
 8659    );
 8660
 8661    // Curly braces and parens autoclose in both HTML and JavaScript.
 8662    cx.update_editor(|editor, window, cx| {
 8663        editor.handle_input(" b=", window, cx);
 8664        editor.handle_input("{", window, cx);
 8665        editor.handle_input("c", window, cx);
 8666        editor.handle_input("(", window, cx);
 8667    });
 8668    cx.assert_editor_state(
 8669        &r#"
 8670            <body><a b={c(ˇ)}>
 8671                <script>
 8672                    var x = 1;<a b={c(ˇ)}
 8673                </script>
 8674            </body><a b={c(ˇ)}>
 8675        "#
 8676        .unindent(),
 8677    );
 8678
 8679    // Brackets that were already autoclosed are skipped.
 8680    cx.update_editor(|editor, window, cx| {
 8681        editor.handle_input(")", window, cx);
 8682        editor.handle_input("d", window, cx);
 8683        editor.handle_input("}", window, cx);
 8684    });
 8685    cx.assert_editor_state(
 8686        &r#"
 8687            <body><a b={c()d}ˇ>
 8688                <script>
 8689                    var x = 1;<a b={c()d}ˇ
 8690                </script>
 8691            </body><a b={c()d}ˇ>
 8692        "#
 8693        .unindent(),
 8694    );
 8695    cx.update_editor(|editor, window, cx| {
 8696        editor.handle_input(">", window, cx);
 8697    });
 8698    cx.assert_editor_state(
 8699        &r#"
 8700            <body><a b={c()d}>ˇ
 8701                <script>
 8702                    var x = 1;<a b={c()d}>ˇ
 8703                </script>
 8704            </body><a b={c()d}>ˇ
 8705        "#
 8706        .unindent(),
 8707    );
 8708
 8709    // Reset
 8710    cx.set_state(
 8711        &r#"
 8712            <body>ˇ
 8713                <script>
 8714                    var x = 1;ˇ
 8715                </script>
 8716            </body>ˇ
 8717        "#
 8718        .unindent(),
 8719    );
 8720
 8721    cx.update_editor(|editor, window, cx| {
 8722        editor.handle_input("<", window, cx);
 8723    });
 8724    cx.assert_editor_state(
 8725        &r#"
 8726            <body><ˇ>
 8727                <script>
 8728                    var x = 1;<ˇ
 8729                </script>
 8730            </body><ˇ>
 8731        "#
 8732        .unindent(),
 8733    );
 8734
 8735    // When backspacing, the closing angle brackets are removed.
 8736    cx.update_editor(|editor, window, cx| {
 8737        editor.backspace(&Backspace, window, cx);
 8738    });
 8739    cx.assert_editor_state(
 8740        &r#"
 8741            <body>ˇ
 8742                <script>
 8743                    var x = 1;ˇ
 8744                </script>
 8745            </body>ˇ
 8746        "#
 8747        .unindent(),
 8748    );
 8749
 8750    // Block comments autoclose in JavaScript, but not HTML.
 8751    cx.update_editor(|editor, window, cx| {
 8752        editor.handle_input("/", window, cx);
 8753        editor.handle_input("*", window, cx);
 8754    });
 8755    cx.assert_editor_state(
 8756        &r#"
 8757            <body>/*ˇ
 8758                <script>
 8759                    var x = 1;/*ˇ */
 8760                </script>
 8761            </body>/*ˇ
 8762        "#
 8763        .unindent(),
 8764    );
 8765}
 8766
 8767#[gpui::test]
 8768async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8769    init_test(cx, |_| {});
 8770
 8771    let mut cx = EditorTestContext::new(cx).await;
 8772
 8773    let rust_language = Arc::new(
 8774        Language::new(
 8775            LanguageConfig {
 8776                name: "Rust".into(),
 8777                brackets: serde_json::from_value(json!([
 8778                    { "start": "{", "end": "}", "close": true, "newline": true },
 8779                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8780                ]))
 8781                .unwrap(),
 8782                autoclose_before: "})]>".into(),
 8783                ..Default::default()
 8784            },
 8785            Some(tree_sitter_rust::LANGUAGE.into()),
 8786        )
 8787        .with_override_query("(string_literal) @string")
 8788        .unwrap(),
 8789    );
 8790
 8791    cx.language_registry().add(rust_language.clone());
 8792    cx.update_buffer(|buffer, cx| {
 8793        buffer.set_language(Some(rust_language), cx);
 8794    });
 8795
 8796    cx.set_state(
 8797        &r#"
 8798            let x = ˇ
 8799        "#
 8800        .unindent(),
 8801    );
 8802
 8803    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8804    cx.update_editor(|editor, window, cx| {
 8805        editor.handle_input("\"", window, cx);
 8806    });
 8807    cx.assert_editor_state(
 8808        &r#"
 8809            let x = "ˇ"
 8810        "#
 8811        .unindent(),
 8812    );
 8813
 8814    // Inserting another quotation mark. The cursor moves across the existing
 8815    // automatically-inserted quotation mark.
 8816    cx.update_editor(|editor, window, cx| {
 8817        editor.handle_input("\"", window, cx);
 8818    });
 8819    cx.assert_editor_state(
 8820        &r#"
 8821            let x = ""ˇ
 8822        "#
 8823        .unindent(),
 8824    );
 8825
 8826    // Reset
 8827    cx.set_state(
 8828        &r#"
 8829            let x = ˇ
 8830        "#
 8831        .unindent(),
 8832    );
 8833
 8834    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8835    cx.update_editor(|editor, window, cx| {
 8836        editor.handle_input("\"", window, cx);
 8837        editor.handle_input(" ", window, cx);
 8838        editor.move_left(&Default::default(), window, cx);
 8839        editor.handle_input("\\", window, cx);
 8840        editor.handle_input("\"", window, cx);
 8841    });
 8842    cx.assert_editor_state(
 8843        &r#"
 8844            let x = "\"ˇ "
 8845        "#
 8846        .unindent(),
 8847    );
 8848
 8849    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8850    // mark. Nothing is inserted.
 8851    cx.update_editor(|editor, window, cx| {
 8852        editor.move_right(&Default::default(), window, cx);
 8853        editor.handle_input("\"", window, cx);
 8854    });
 8855    cx.assert_editor_state(
 8856        &r#"
 8857            let x = "\" "ˇ
 8858        "#
 8859        .unindent(),
 8860    );
 8861}
 8862
 8863#[gpui::test]
 8864async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8865    init_test(cx, |_| {});
 8866
 8867    let language = Arc::new(Language::new(
 8868        LanguageConfig {
 8869            brackets: BracketPairConfig {
 8870                pairs: vec![
 8871                    BracketPair {
 8872                        start: "{".to_string(),
 8873                        end: "}".to_string(),
 8874                        close: true,
 8875                        surround: true,
 8876                        newline: true,
 8877                    },
 8878                    BracketPair {
 8879                        start: "/* ".to_string(),
 8880                        end: "*/".to_string(),
 8881                        close: true,
 8882                        surround: true,
 8883                        ..Default::default()
 8884                    },
 8885                ],
 8886                ..Default::default()
 8887            },
 8888            ..Default::default()
 8889        },
 8890        Some(tree_sitter_rust::LANGUAGE.into()),
 8891    ));
 8892
 8893    let text = r#"
 8894        a
 8895        b
 8896        c
 8897    "#
 8898    .unindent();
 8899
 8900    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8901    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8902    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8903    editor
 8904        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8905        .await;
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8909            s.select_display_ranges([
 8910                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8911                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8913            ])
 8914        });
 8915
 8916        editor.handle_input("{", window, cx);
 8917        editor.handle_input("{", window, cx);
 8918        editor.handle_input("{", window, cx);
 8919        assert_eq!(
 8920            editor.text(cx),
 8921            "
 8922                {{{a}}}
 8923                {{{b}}}
 8924                {{{c}}}
 8925            "
 8926            .unindent()
 8927        );
 8928        assert_eq!(
 8929            editor.selections.display_ranges(cx),
 8930            [
 8931                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8932                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8933                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8934            ]
 8935        );
 8936
 8937        editor.undo(&Undo, window, cx);
 8938        editor.undo(&Undo, window, cx);
 8939        editor.undo(&Undo, window, cx);
 8940        assert_eq!(
 8941            editor.text(cx),
 8942            "
 8943                a
 8944                b
 8945                c
 8946            "
 8947            .unindent()
 8948        );
 8949        assert_eq!(
 8950            editor.selections.display_ranges(cx),
 8951            [
 8952                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8953                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8954                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8955            ]
 8956        );
 8957
 8958        // Ensure inserting the first character of a multi-byte bracket pair
 8959        // doesn't surround the selections with the bracket.
 8960        editor.handle_input("/", window, cx);
 8961        assert_eq!(
 8962            editor.text(cx),
 8963            "
 8964                /
 8965                /
 8966                /
 8967            "
 8968            .unindent()
 8969        );
 8970        assert_eq!(
 8971            editor.selections.display_ranges(cx),
 8972            [
 8973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8974                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8975                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8976            ]
 8977        );
 8978
 8979        editor.undo(&Undo, window, cx);
 8980        assert_eq!(
 8981            editor.text(cx),
 8982            "
 8983                a
 8984                b
 8985                c
 8986            "
 8987            .unindent()
 8988        );
 8989        assert_eq!(
 8990            editor.selections.display_ranges(cx),
 8991            [
 8992                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8993                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8994                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8995            ]
 8996        );
 8997
 8998        // Ensure inserting the last character of a multi-byte bracket pair
 8999        // doesn't surround the selections with the bracket.
 9000        editor.handle_input("*", window, cx);
 9001        assert_eq!(
 9002            editor.text(cx),
 9003            "
 9004                *
 9005                *
 9006                *
 9007            "
 9008            .unindent()
 9009        );
 9010        assert_eq!(
 9011            editor.selections.display_ranges(cx),
 9012            [
 9013                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9014                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9015                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9016            ]
 9017        );
 9018    });
 9019}
 9020
 9021#[gpui::test]
 9022async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9023    init_test(cx, |_| {});
 9024
 9025    let language = Arc::new(Language::new(
 9026        LanguageConfig {
 9027            brackets: BracketPairConfig {
 9028                pairs: vec![BracketPair {
 9029                    start: "{".to_string(),
 9030                    end: "}".to_string(),
 9031                    close: true,
 9032                    surround: true,
 9033                    newline: true,
 9034                }],
 9035                ..Default::default()
 9036            },
 9037            autoclose_before: "}".to_string(),
 9038            ..Default::default()
 9039        },
 9040        Some(tree_sitter_rust::LANGUAGE.into()),
 9041    ));
 9042
 9043    let text = r#"
 9044        a
 9045        b
 9046        c
 9047    "#
 9048    .unindent();
 9049
 9050    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9051    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9052    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9053    editor
 9054        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9055        .await;
 9056
 9057    editor.update_in(cx, |editor, window, cx| {
 9058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9059            s.select_ranges([
 9060                Point::new(0, 1)..Point::new(0, 1),
 9061                Point::new(1, 1)..Point::new(1, 1),
 9062                Point::new(2, 1)..Point::new(2, 1),
 9063            ])
 9064        });
 9065
 9066        editor.handle_input("{", window, cx);
 9067        editor.handle_input("{", window, cx);
 9068        editor.handle_input("_", window, cx);
 9069        assert_eq!(
 9070            editor.text(cx),
 9071            "
 9072                a{{_}}
 9073                b{{_}}
 9074                c{{_}}
 9075            "
 9076            .unindent()
 9077        );
 9078        assert_eq!(
 9079            editor.selections.ranges::<Point>(cx),
 9080            [
 9081                Point::new(0, 4)..Point::new(0, 4),
 9082                Point::new(1, 4)..Point::new(1, 4),
 9083                Point::new(2, 4)..Point::new(2, 4)
 9084            ]
 9085        );
 9086
 9087        editor.backspace(&Default::default(), window, cx);
 9088        editor.backspace(&Default::default(), window, cx);
 9089        assert_eq!(
 9090            editor.text(cx),
 9091            "
 9092                a{}
 9093                b{}
 9094                c{}
 9095            "
 9096            .unindent()
 9097        );
 9098        assert_eq!(
 9099            editor.selections.ranges::<Point>(cx),
 9100            [
 9101                Point::new(0, 2)..Point::new(0, 2),
 9102                Point::new(1, 2)..Point::new(1, 2),
 9103                Point::new(2, 2)..Point::new(2, 2)
 9104            ]
 9105        );
 9106
 9107        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9108        assert_eq!(
 9109            editor.text(cx),
 9110            "
 9111                a
 9112                b
 9113                c
 9114            "
 9115            .unindent()
 9116        );
 9117        assert_eq!(
 9118            editor.selections.ranges::<Point>(cx),
 9119            [
 9120                Point::new(0, 1)..Point::new(0, 1),
 9121                Point::new(1, 1)..Point::new(1, 1),
 9122                Point::new(2, 1)..Point::new(2, 1)
 9123            ]
 9124        );
 9125    });
 9126}
 9127
 9128#[gpui::test]
 9129async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9130    init_test(cx, |settings| {
 9131        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9132    });
 9133
 9134    let mut cx = EditorTestContext::new(cx).await;
 9135
 9136    let language = Arc::new(Language::new(
 9137        LanguageConfig {
 9138            brackets: BracketPairConfig {
 9139                pairs: vec![
 9140                    BracketPair {
 9141                        start: "{".to_string(),
 9142                        end: "}".to_string(),
 9143                        close: true,
 9144                        surround: true,
 9145                        newline: true,
 9146                    },
 9147                    BracketPair {
 9148                        start: "(".to_string(),
 9149                        end: ")".to_string(),
 9150                        close: true,
 9151                        surround: true,
 9152                        newline: true,
 9153                    },
 9154                    BracketPair {
 9155                        start: "[".to_string(),
 9156                        end: "]".to_string(),
 9157                        close: false,
 9158                        surround: true,
 9159                        newline: true,
 9160                    },
 9161                ],
 9162                ..Default::default()
 9163            },
 9164            autoclose_before: "})]".to_string(),
 9165            ..Default::default()
 9166        },
 9167        Some(tree_sitter_rust::LANGUAGE.into()),
 9168    ));
 9169
 9170    cx.language_registry().add(language.clone());
 9171    cx.update_buffer(|buffer, cx| {
 9172        buffer.set_language(Some(language), cx);
 9173    });
 9174
 9175    cx.set_state(
 9176        &"
 9177            {(ˇ)}
 9178            [[ˇ]]
 9179            {(ˇ)}
 9180        "
 9181        .unindent(),
 9182    );
 9183
 9184    cx.update_editor(|editor, window, cx| {
 9185        editor.backspace(&Default::default(), window, cx);
 9186        editor.backspace(&Default::default(), window, cx);
 9187    });
 9188
 9189    cx.assert_editor_state(
 9190        &"
 9191            ˇ
 9192            ˇ]]
 9193            ˇ
 9194        "
 9195        .unindent(),
 9196    );
 9197
 9198    cx.update_editor(|editor, window, cx| {
 9199        editor.handle_input("{", window, cx);
 9200        editor.handle_input("{", window, cx);
 9201        editor.move_right(&MoveRight, window, cx);
 9202        editor.move_right(&MoveRight, window, cx);
 9203        editor.move_left(&MoveLeft, window, cx);
 9204        editor.move_left(&MoveLeft, window, cx);
 9205        editor.backspace(&Default::default(), window, cx);
 9206    });
 9207
 9208    cx.assert_editor_state(
 9209        &"
 9210            {ˇ}
 9211            {ˇ}]]
 9212            {ˇ}
 9213        "
 9214        .unindent(),
 9215    );
 9216
 9217    cx.update_editor(|editor, window, cx| {
 9218        editor.backspace(&Default::default(), window, cx);
 9219    });
 9220
 9221    cx.assert_editor_state(
 9222        &"
 9223            ˇ
 9224            ˇ]]
 9225            ˇ
 9226        "
 9227        .unindent(),
 9228    );
 9229}
 9230
 9231#[gpui::test]
 9232async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9233    init_test(cx, |_| {});
 9234
 9235    let language = Arc::new(Language::new(
 9236        LanguageConfig::default(),
 9237        Some(tree_sitter_rust::LANGUAGE.into()),
 9238    ));
 9239
 9240    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9241    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9242    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9243    editor
 9244        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9245        .await;
 9246
 9247    editor.update_in(cx, |editor, window, cx| {
 9248        editor.set_auto_replace_emoji_shortcode(true);
 9249
 9250        editor.handle_input("Hello ", window, cx);
 9251        editor.handle_input(":wave", window, cx);
 9252        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9253
 9254        editor.handle_input(":", window, cx);
 9255        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9256
 9257        editor.handle_input(" :smile", window, cx);
 9258        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9259
 9260        editor.handle_input(":", window, cx);
 9261        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9262
 9263        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9264        editor.handle_input(":wave", window, cx);
 9265        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9266
 9267        editor.handle_input(":", window, cx);
 9268        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9269
 9270        editor.handle_input(":1", window, cx);
 9271        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9272
 9273        editor.handle_input(":", window, cx);
 9274        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9275
 9276        // Ensure shortcode does not get replaced when it is part of a word
 9277        editor.handle_input(" Test:wave", window, cx);
 9278        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9279
 9280        editor.handle_input(":", window, cx);
 9281        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9282
 9283        editor.set_auto_replace_emoji_shortcode(false);
 9284
 9285        // Ensure shortcode does not get replaced when auto replace is off
 9286        editor.handle_input(" :wave", window, cx);
 9287        assert_eq!(
 9288            editor.text(cx),
 9289            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9290        );
 9291
 9292        editor.handle_input(":", window, cx);
 9293        assert_eq!(
 9294            editor.text(cx),
 9295            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9296        );
 9297    });
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    let (text, insertion_ranges) = marked_text_ranges(
 9305        indoc! {"
 9306            ˇ
 9307        "},
 9308        false,
 9309    );
 9310
 9311    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9312    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9313
 9314    _ = editor.update_in(cx, |editor, window, cx| {
 9315        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9316
 9317        editor
 9318            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9319            .unwrap();
 9320
 9321        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9322            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9323            assert_eq!(editor.text(cx), expected_text);
 9324            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9325        }
 9326
 9327        assert(
 9328            editor,
 9329            cx,
 9330            indoc! {"
 9331            type «» =•
 9332            "},
 9333        );
 9334
 9335        assert!(editor.context_menu_visible(), "There should be a matches");
 9336    });
 9337}
 9338
 9339#[gpui::test]
 9340async fn test_snippets(cx: &mut TestAppContext) {
 9341    init_test(cx, |_| {});
 9342
 9343    let mut cx = EditorTestContext::new(cx).await;
 9344
 9345    cx.set_state(indoc! {"
 9346        a.ˇ b
 9347        a.ˇ b
 9348        a.ˇ b
 9349    "});
 9350
 9351    cx.update_editor(|editor, window, cx| {
 9352        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9353        let insertion_ranges = editor
 9354            .selections
 9355            .all(cx)
 9356            .iter()
 9357            .map(|s| s.range().clone())
 9358            .collect::<Vec<_>>();
 9359        editor
 9360            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9361            .unwrap();
 9362    });
 9363
 9364    cx.assert_editor_state(indoc! {"
 9365        a.f(«oneˇ», two, «threeˇ») b
 9366        a.f(«oneˇ», two, «threeˇ») b
 9367        a.f(«oneˇ», two, «threeˇ») b
 9368    "});
 9369
 9370    // Can't move earlier than the first tab stop
 9371    cx.update_editor(|editor, window, cx| {
 9372        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9373    });
 9374    cx.assert_editor_state(indoc! {"
 9375        a.f(«oneˇ», two, «threeˇ») b
 9376        a.f(«oneˇ», two, «threeˇ») b
 9377        a.f(«oneˇ», two, «threeˇ») b
 9378    "});
 9379
 9380    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9381    cx.assert_editor_state(indoc! {"
 9382        a.f(one, «twoˇ», three) b
 9383        a.f(one, «twoˇ», three) b
 9384        a.f(one, «twoˇ», three) b
 9385    "});
 9386
 9387    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9388    cx.assert_editor_state(indoc! {"
 9389        a.f(«oneˇ», two, «threeˇ») b
 9390        a.f(«oneˇ», two, «threeˇ») b
 9391        a.f(«oneˇ», two, «threeˇ») b
 9392    "});
 9393
 9394    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9395    cx.assert_editor_state(indoc! {"
 9396        a.f(one, «twoˇ», three) b
 9397        a.f(one, «twoˇ», three) b
 9398        a.f(one, «twoˇ», three) b
 9399    "});
 9400    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9401    cx.assert_editor_state(indoc! {"
 9402        a.f(one, two, three)ˇ b
 9403        a.f(one, two, three)ˇ b
 9404        a.f(one, two, three)ˇ b
 9405    "});
 9406
 9407    // As soon as the last tab stop is reached, snippet state is gone
 9408    cx.update_editor(|editor, window, cx| {
 9409        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9410    });
 9411    cx.assert_editor_state(indoc! {"
 9412        a.f(one, two, three)ˇ b
 9413        a.f(one, two, three)ˇ b
 9414        a.f(one, two, three)ˇ b
 9415    "});
 9416}
 9417
 9418#[gpui::test]
 9419async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9420    init_test(cx, |_| {});
 9421
 9422    let mut cx = EditorTestContext::new(cx).await;
 9423
 9424    cx.update_editor(|editor, window, cx| {
 9425        let snippet = Snippet::parse(indoc! {"
 9426            /*
 9427             * Multiline comment with leading indentation
 9428             *
 9429             * $1
 9430             */
 9431            $0"})
 9432        .unwrap();
 9433        let insertion_ranges = editor
 9434            .selections
 9435            .all(cx)
 9436            .iter()
 9437            .map(|s| s.range().clone())
 9438            .collect::<Vec<_>>();
 9439        editor
 9440            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9441            .unwrap();
 9442    });
 9443
 9444    cx.assert_editor_state(indoc! {"
 9445        /*
 9446         * Multiline comment with leading indentation
 9447         *
 9448         * ˇ
 9449         */
 9450    "});
 9451
 9452    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9453    cx.assert_editor_state(indoc! {"
 9454        /*
 9455         * Multiline comment with leading indentation
 9456         *
 9457         *•
 9458         */
 9459        ˇ"});
 9460}
 9461
 9462#[gpui::test]
 9463async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9464    init_test(cx, |_| {});
 9465
 9466    let fs = FakeFs::new(cx.executor());
 9467    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9468
 9469    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9470
 9471    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9472    language_registry.add(rust_lang());
 9473    let mut fake_servers = language_registry.register_fake_lsp(
 9474        "Rust",
 9475        FakeLspAdapter {
 9476            capabilities: lsp::ServerCapabilities {
 9477                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9478                ..Default::default()
 9479            },
 9480            ..Default::default()
 9481        },
 9482    );
 9483
 9484    let buffer = project
 9485        .update(cx, |project, cx| {
 9486            project.open_local_buffer(path!("/file.rs"), cx)
 9487        })
 9488        .await
 9489        .unwrap();
 9490
 9491    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9492    let (editor, cx) = cx.add_window_view(|window, cx| {
 9493        build_editor_with_project(project.clone(), buffer, window, cx)
 9494    });
 9495    editor.update_in(cx, |editor, window, cx| {
 9496        editor.set_text("one\ntwo\nthree\n", window, cx)
 9497    });
 9498    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9499
 9500    cx.executor().start_waiting();
 9501    let fake_server = fake_servers.next().await.unwrap();
 9502
 9503    {
 9504        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9505            move |params, _| async move {
 9506                assert_eq!(
 9507                    params.text_document.uri,
 9508                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9509                );
 9510                assert_eq!(params.options.tab_size, 4);
 9511                Ok(Some(vec![lsp::TextEdit::new(
 9512                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9513                    ", ".to_string(),
 9514                )]))
 9515            },
 9516        );
 9517        let save = editor
 9518            .update_in(cx, |editor, window, cx| {
 9519                editor.save(
 9520                    SaveOptions {
 9521                        format: true,
 9522                        autosave: false,
 9523                    },
 9524                    project.clone(),
 9525                    window,
 9526                    cx,
 9527                )
 9528            })
 9529            .unwrap();
 9530        cx.executor().start_waiting();
 9531        save.await;
 9532
 9533        assert_eq!(
 9534            editor.update(cx, |editor, cx| editor.text(cx)),
 9535            "one, two\nthree\n"
 9536        );
 9537        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9538    }
 9539
 9540    {
 9541        editor.update_in(cx, |editor, window, cx| {
 9542            editor.set_text("one\ntwo\nthree\n", window, cx)
 9543        });
 9544        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9545
 9546        // Ensure we can still save even if formatting hangs.
 9547        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9548            move |params, _| async move {
 9549                assert_eq!(
 9550                    params.text_document.uri,
 9551                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9552                );
 9553                futures::future::pending::<()>().await;
 9554                unreachable!()
 9555            },
 9556        );
 9557        let save = editor
 9558            .update_in(cx, |editor, window, cx| {
 9559                editor.save(
 9560                    SaveOptions {
 9561                        format: true,
 9562                        autosave: false,
 9563                    },
 9564                    project.clone(),
 9565                    window,
 9566                    cx,
 9567                )
 9568            })
 9569            .unwrap();
 9570        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9571        cx.executor().start_waiting();
 9572        save.await;
 9573        assert_eq!(
 9574            editor.update(cx, |editor, cx| editor.text(cx)),
 9575            "one\ntwo\nthree\n"
 9576        );
 9577    }
 9578
 9579    // Set rust language override and assert overridden tabsize is sent to language server
 9580    update_test_language_settings(cx, |settings| {
 9581        settings.languages.0.insert(
 9582            "Rust".into(),
 9583            LanguageSettingsContent {
 9584                tab_size: NonZeroU32::new(8),
 9585                ..Default::default()
 9586            },
 9587        );
 9588    });
 9589
 9590    {
 9591        editor.update_in(cx, |editor, window, cx| {
 9592            editor.set_text("somehting_new\n", window, cx)
 9593        });
 9594        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9595        let _formatting_request_signal = fake_server
 9596            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9597                assert_eq!(
 9598                    params.text_document.uri,
 9599                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9600                );
 9601                assert_eq!(params.options.tab_size, 8);
 9602                Ok(Some(vec![]))
 9603            });
 9604        let save = editor
 9605            .update_in(cx, |editor, window, cx| {
 9606                editor.save(
 9607                    SaveOptions {
 9608                        format: true,
 9609                        autosave: false,
 9610                    },
 9611                    project.clone(),
 9612                    window,
 9613                    cx,
 9614                )
 9615            })
 9616            .unwrap();
 9617        cx.executor().start_waiting();
 9618        save.await;
 9619    }
 9620}
 9621
 9622#[gpui::test]
 9623async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9624    init_test(cx, |settings| {
 9625        settings.defaults.ensure_final_newline_on_save = Some(false);
 9626    });
 9627
 9628    let fs = FakeFs::new(cx.executor());
 9629    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9630
 9631    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9632
 9633    let buffer = project
 9634        .update(cx, |project, cx| {
 9635            project.open_local_buffer(path!("/file.txt"), cx)
 9636        })
 9637        .await
 9638        .unwrap();
 9639
 9640    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9641    let (editor, cx) = cx.add_window_view(|window, cx| {
 9642        build_editor_with_project(project.clone(), buffer, window, cx)
 9643    });
 9644    editor.update_in(cx, |editor, window, cx| {
 9645        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9646            s.select_ranges([0..0])
 9647        });
 9648    });
 9649    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9650
 9651    editor.update_in(cx, |editor, window, cx| {
 9652        editor.handle_input("\n", window, cx)
 9653    });
 9654    cx.run_until_parked();
 9655    save(&editor, &project, cx).await;
 9656    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9657
 9658    editor.update_in(cx, |editor, window, cx| {
 9659        editor.undo(&Default::default(), window, cx);
 9660    });
 9661    save(&editor, &project, cx).await;
 9662    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9663
 9664    editor.update_in(cx, |editor, window, cx| {
 9665        editor.redo(&Default::default(), window, cx);
 9666    });
 9667    cx.run_until_parked();
 9668    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9669
 9670    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9671        let save = editor
 9672            .update_in(cx, |editor, window, cx| {
 9673                editor.save(
 9674                    SaveOptions {
 9675                        format: true,
 9676                        autosave: false,
 9677                    },
 9678                    project.clone(),
 9679                    window,
 9680                    cx,
 9681                )
 9682            })
 9683            .unwrap();
 9684        cx.executor().start_waiting();
 9685        save.await;
 9686        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9687    }
 9688}
 9689
 9690#[gpui::test]
 9691async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9692    init_test(cx, |_| {});
 9693
 9694    let cols = 4;
 9695    let rows = 10;
 9696    let sample_text_1 = sample_text(rows, cols, 'a');
 9697    assert_eq!(
 9698        sample_text_1,
 9699        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9700    );
 9701    let sample_text_2 = sample_text(rows, cols, 'l');
 9702    assert_eq!(
 9703        sample_text_2,
 9704        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9705    );
 9706    let sample_text_3 = sample_text(rows, cols, 'v');
 9707    assert_eq!(
 9708        sample_text_3,
 9709        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9710    );
 9711
 9712    let fs = FakeFs::new(cx.executor());
 9713    fs.insert_tree(
 9714        path!("/a"),
 9715        json!({
 9716            "main.rs": sample_text_1,
 9717            "other.rs": sample_text_2,
 9718            "lib.rs": sample_text_3,
 9719        }),
 9720    )
 9721    .await;
 9722
 9723    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9725    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9726
 9727    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9728    language_registry.add(rust_lang());
 9729    let mut fake_servers = language_registry.register_fake_lsp(
 9730        "Rust",
 9731        FakeLspAdapter {
 9732            capabilities: lsp::ServerCapabilities {
 9733                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9734                ..Default::default()
 9735            },
 9736            ..Default::default()
 9737        },
 9738    );
 9739
 9740    let worktree = project.update(cx, |project, cx| {
 9741        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9742        assert_eq!(worktrees.len(), 1);
 9743        worktrees.pop().unwrap()
 9744    });
 9745    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9746
 9747    let buffer_1 = project
 9748        .update(cx, |project, cx| {
 9749            project.open_buffer((worktree_id, "main.rs"), cx)
 9750        })
 9751        .await
 9752        .unwrap();
 9753    let buffer_2 = project
 9754        .update(cx, |project, cx| {
 9755            project.open_buffer((worktree_id, "other.rs"), cx)
 9756        })
 9757        .await
 9758        .unwrap();
 9759    let buffer_3 = project
 9760        .update(cx, |project, cx| {
 9761            project.open_buffer((worktree_id, "lib.rs"), cx)
 9762        })
 9763        .await
 9764        .unwrap();
 9765
 9766    let multi_buffer = cx.new(|cx| {
 9767        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9768        multi_buffer.push_excerpts(
 9769            buffer_1.clone(),
 9770            [
 9771                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9772                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9773                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9774            ],
 9775            cx,
 9776        );
 9777        multi_buffer.push_excerpts(
 9778            buffer_2.clone(),
 9779            [
 9780                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9781                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9782                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9783            ],
 9784            cx,
 9785        );
 9786        multi_buffer.push_excerpts(
 9787            buffer_3.clone(),
 9788            [
 9789                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9790                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9791                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9792            ],
 9793            cx,
 9794        );
 9795        multi_buffer
 9796    });
 9797    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9798        Editor::new(
 9799            EditorMode::full(),
 9800            multi_buffer,
 9801            Some(project.clone()),
 9802            window,
 9803            cx,
 9804        )
 9805    });
 9806
 9807    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9808        editor.change_selections(
 9809            SelectionEffects::scroll(Autoscroll::Next),
 9810            window,
 9811            cx,
 9812            |s| s.select_ranges(Some(1..2)),
 9813        );
 9814        editor.insert("|one|two|three|", window, cx);
 9815    });
 9816    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9817    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9818        editor.change_selections(
 9819            SelectionEffects::scroll(Autoscroll::Next),
 9820            window,
 9821            cx,
 9822            |s| s.select_ranges(Some(60..70)),
 9823        );
 9824        editor.insert("|four|five|six|", window, cx);
 9825    });
 9826    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9827
 9828    // First two buffers should be edited, but not the third one.
 9829    assert_eq!(
 9830        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9831        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 9832    );
 9833    buffer_1.update(cx, |buffer, _| {
 9834        assert!(buffer.is_dirty());
 9835        assert_eq!(
 9836            buffer.text(),
 9837            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9838        )
 9839    });
 9840    buffer_2.update(cx, |buffer, _| {
 9841        assert!(buffer.is_dirty());
 9842        assert_eq!(
 9843            buffer.text(),
 9844            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9845        )
 9846    });
 9847    buffer_3.update(cx, |buffer, _| {
 9848        assert!(!buffer.is_dirty());
 9849        assert_eq!(buffer.text(), sample_text_3,)
 9850    });
 9851    cx.executor().run_until_parked();
 9852
 9853    cx.executor().start_waiting();
 9854    let save = multi_buffer_editor
 9855        .update_in(cx, |editor, window, cx| {
 9856            editor.save(
 9857                SaveOptions {
 9858                    format: true,
 9859                    autosave: false,
 9860                },
 9861                project.clone(),
 9862                window,
 9863                cx,
 9864            )
 9865        })
 9866        .unwrap();
 9867
 9868    let fake_server = fake_servers.next().await.unwrap();
 9869    fake_server
 9870        .server
 9871        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9872            Ok(Some(vec![lsp::TextEdit::new(
 9873                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9874                format!("[{} formatted]", params.text_document.uri),
 9875            )]))
 9876        })
 9877        .detach();
 9878    save.await;
 9879
 9880    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9881    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9882    assert_eq!(
 9883        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9884        uri!(
 9885            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9886        ),
 9887    );
 9888    buffer_1.update(cx, |buffer, _| {
 9889        assert!(!buffer.is_dirty());
 9890        assert_eq!(
 9891            buffer.text(),
 9892            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9893        )
 9894    });
 9895    buffer_2.update(cx, |buffer, _| {
 9896        assert!(!buffer.is_dirty());
 9897        assert_eq!(
 9898            buffer.text(),
 9899            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9900        )
 9901    });
 9902    buffer_3.update(cx, |buffer, _| {
 9903        assert!(!buffer.is_dirty());
 9904        assert_eq!(buffer.text(), sample_text_3,)
 9905    });
 9906}
 9907
 9908#[gpui::test]
 9909async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9910    init_test(cx, |_| {});
 9911
 9912    let fs = FakeFs::new(cx.executor());
 9913    fs.insert_tree(
 9914        path!("/dir"),
 9915        json!({
 9916            "file1.rs": "fn main() { println!(\"hello\"); }",
 9917            "file2.rs": "fn test() { println!(\"test\"); }",
 9918            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9919        }),
 9920    )
 9921    .await;
 9922
 9923    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9924    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9925    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9926
 9927    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9928    language_registry.add(rust_lang());
 9929
 9930    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9931    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9932
 9933    // Open three buffers
 9934    let buffer_1 = project
 9935        .update(cx, |project, cx| {
 9936            project.open_buffer((worktree_id, "file1.rs"), cx)
 9937        })
 9938        .await
 9939        .unwrap();
 9940    let buffer_2 = project
 9941        .update(cx, |project, cx| {
 9942            project.open_buffer((worktree_id, "file2.rs"), cx)
 9943        })
 9944        .await
 9945        .unwrap();
 9946    let buffer_3 = project
 9947        .update(cx, |project, cx| {
 9948            project.open_buffer((worktree_id, "file3.rs"), cx)
 9949        })
 9950        .await
 9951        .unwrap();
 9952
 9953    // Create a multi-buffer with all three buffers
 9954    let multi_buffer = cx.new(|cx| {
 9955        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9956        multi_buffer.push_excerpts(
 9957            buffer_1.clone(),
 9958            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9959            cx,
 9960        );
 9961        multi_buffer.push_excerpts(
 9962            buffer_2.clone(),
 9963            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9964            cx,
 9965        );
 9966        multi_buffer.push_excerpts(
 9967            buffer_3.clone(),
 9968            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9969            cx,
 9970        );
 9971        multi_buffer
 9972    });
 9973
 9974    let editor = cx.new_window_entity(|window, cx| {
 9975        Editor::new(
 9976            EditorMode::full(),
 9977            multi_buffer,
 9978            Some(project.clone()),
 9979            window,
 9980            cx,
 9981        )
 9982    });
 9983
 9984    // Edit only the first buffer
 9985    editor.update_in(cx, |editor, window, cx| {
 9986        editor.change_selections(
 9987            SelectionEffects::scroll(Autoscroll::Next),
 9988            window,
 9989            cx,
 9990            |s| s.select_ranges(Some(10..10)),
 9991        );
 9992        editor.insert("// edited", window, cx);
 9993    });
 9994
 9995    // Verify that only buffer 1 is dirty
 9996    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9997    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9998    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9999
10000    // Get write counts after file creation (files were created with initial content)
10001    // We expect each file to have been written once during creation
10002    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10003    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10004    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10005
10006    // Perform autosave
10007    let save_task = editor.update_in(cx, |editor, window, cx| {
10008        editor.save(
10009            SaveOptions {
10010                format: true,
10011                autosave: true,
10012            },
10013            project.clone(),
10014            window,
10015            cx,
10016        )
10017    });
10018    save_task.await.unwrap();
10019
10020    // Only the dirty buffer should have been saved
10021    assert_eq!(
10022        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10023        1,
10024        "Buffer 1 was dirty, so it should have been written once during autosave"
10025    );
10026    assert_eq!(
10027        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10028        0,
10029        "Buffer 2 was clean, so it should not have been written during autosave"
10030    );
10031    assert_eq!(
10032        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10033        0,
10034        "Buffer 3 was clean, so it should not have been written during autosave"
10035    );
10036
10037    // Verify buffer states after autosave
10038    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10039    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10040    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10041
10042    // Now perform a manual save (format = true)
10043    let save_task = editor.update_in(cx, |editor, window, cx| {
10044        editor.save(
10045            SaveOptions {
10046                format: true,
10047                autosave: false,
10048            },
10049            project.clone(),
10050            window,
10051            cx,
10052        )
10053    });
10054    save_task.await.unwrap();
10055
10056    // During manual save, clean buffers don't get written to disk
10057    // They just get did_save called for language server notifications
10058    assert_eq!(
10059        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10060        1,
10061        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10062    );
10063    assert_eq!(
10064        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10065        0,
10066        "Buffer 2 should not have been written at all"
10067    );
10068    assert_eq!(
10069        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10070        0,
10071        "Buffer 3 should not have been written at all"
10072    );
10073}
10074
10075#[gpui::test]
10076async fn test_range_format_during_save(cx: &mut TestAppContext) {
10077    init_test(cx, |_| {});
10078
10079    let fs = FakeFs::new(cx.executor());
10080    fs.insert_file(path!("/file.rs"), Default::default()).await;
10081
10082    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10083
10084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10085    language_registry.add(rust_lang());
10086    let mut fake_servers = language_registry.register_fake_lsp(
10087        "Rust",
10088        FakeLspAdapter {
10089            capabilities: lsp::ServerCapabilities {
10090                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10091                ..Default::default()
10092            },
10093            ..Default::default()
10094        },
10095    );
10096
10097    let buffer = project
10098        .update(cx, |project, cx| {
10099            project.open_local_buffer(path!("/file.rs"), cx)
10100        })
10101        .await
10102        .unwrap();
10103
10104    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10105    let (editor, cx) = cx.add_window_view(|window, cx| {
10106        build_editor_with_project(project.clone(), buffer, window, cx)
10107    });
10108    editor.update_in(cx, |editor, window, cx| {
10109        editor.set_text("one\ntwo\nthree\n", window, cx)
10110    });
10111    assert!(cx.read(|cx| editor.is_dirty(cx)));
10112
10113    cx.executor().start_waiting();
10114    let fake_server = fake_servers.next().await.unwrap();
10115
10116    let save = editor
10117        .update_in(cx, |editor, window, cx| {
10118            editor.save(
10119                SaveOptions {
10120                    format: true,
10121                    autosave: false,
10122                },
10123                project.clone(),
10124                window,
10125                cx,
10126            )
10127        })
10128        .unwrap();
10129    fake_server
10130        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10131            assert_eq!(
10132                params.text_document.uri,
10133                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10134            );
10135            assert_eq!(params.options.tab_size, 4);
10136            Ok(Some(vec![lsp::TextEdit::new(
10137                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10138                ", ".to_string(),
10139            )]))
10140        })
10141        .next()
10142        .await;
10143    cx.executor().start_waiting();
10144    save.await;
10145    assert_eq!(
10146        editor.update(cx, |editor, cx| editor.text(cx)),
10147        "one, two\nthree\n"
10148    );
10149    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10150
10151    editor.update_in(cx, |editor, window, cx| {
10152        editor.set_text("one\ntwo\nthree\n", window, cx)
10153    });
10154    assert!(cx.read(|cx| editor.is_dirty(cx)));
10155
10156    // Ensure we can still save even if formatting hangs.
10157    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10158        move |params, _| async move {
10159            assert_eq!(
10160                params.text_document.uri,
10161                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10162            );
10163            futures::future::pending::<()>().await;
10164            unreachable!()
10165        },
10166    );
10167    let save = editor
10168        .update_in(cx, |editor, window, cx| {
10169            editor.save(
10170                SaveOptions {
10171                    format: true,
10172                    autosave: false,
10173                },
10174                project.clone(),
10175                window,
10176                cx,
10177            )
10178        })
10179        .unwrap();
10180    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10181    cx.executor().start_waiting();
10182    save.await;
10183    assert_eq!(
10184        editor.update(cx, |editor, cx| editor.text(cx)),
10185        "one\ntwo\nthree\n"
10186    );
10187    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10188
10189    // For non-dirty buffer, no formatting request should be sent
10190    let save = editor
10191        .update_in(cx, |editor, window, cx| {
10192            editor.save(
10193                SaveOptions {
10194                    format: false,
10195                    autosave: false,
10196                },
10197                project.clone(),
10198                window,
10199                cx,
10200            )
10201        })
10202        .unwrap();
10203    let _pending_format_request = fake_server
10204        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10205            panic!("Should not be invoked");
10206        })
10207        .next();
10208    cx.executor().start_waiting();
10209    save.await;
10210
10211    // Set Rust language override and assert overridden tabsize is sent to language server
10212    update_test_language_settings(cx, |settings| {
10213        settings.languages.0.insert(
10214            "Rust".into(),
10215            LanguageSettingsContent {
10216                tab_size: NonZeroU32::new(8),
10217                ..Default::default()
10218            },
10219        );
10220    });
10221
10222    editor.update_in(cx, |editor, window, cx| {
10223        editor.set_text("somehting_new\n", window, cx)
10224    });
10225    assert!(cx.read(|cx| editor.is_dirty(cx)));
10226    let save = editor
10227        .update_in(cx, |editor, window, cx| {
10228            editor.save(
10229                SaveOptions {
10230                    format: true,
10231                    autosave: false,
10232                },
10233                project.clone(),
10234                window,
10235                cx,
10236            )
10237        })
10238        .unwrap();
10239    fake_server
10240        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10241            assert_eq!(
10242                params.text_document.uri,
10243                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10244            );
10245            assert_eq!(params.options.tab_size, 8);
10246            Ok(Some(Vec::new()))
10247        })
10248        .next()
10249        .await;
10250    save.await;
10251}
10252
10253#[gpui::test]
10254async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10255    init_test(cx, |settings| {
10256        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10257            Formatter::LanguageServer { name: None },
10258        )))
10259    });
10260
10261    let fs = FakeFs::new(cx.executor());
10262    fs.insert_file(path!("/file.rs"), Default::default()).await;
10263
10264    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10265
10266    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10267    language_registry.add(Arc::new(Language::new(
10268        LanguageConfig {
10269            name: "Rust".into(),
10270            matcher: LanguageMatcher {
10271                path_suffixes: vec!["rs".to_string()],
10272                ..Default::default()
10273            },
10274            ..LanguageConfig::default()
10275        },
10276        Some(tree_sitter_rust::LANGUAGE.into()),
10277    )));
10278    update_test_language_settings(cx, |settings| {
10279        // Enable Prettier formatting for the same buffer, and ensure
10280        // LSP is called instead of Prettier.
10281        settings.defaults.prettier = Some(PrettierSettings {
10282            allowed: true,
10283            ..PrettierSettings::default()
10284        });
10285    });
10286    let mut fake_servers = language_registry.register_fake_lsp(
10287        "Rust",
10288        FakeLspAdapter {
10289            capabilities: lsp::ServerCapabilities {
10290                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10291                ..Default::default()
10292            },
10293            ..Default::default()
10294        },
10295    );
10296
10297    let buffer = project
10298        .update(cx, |project, cx| {
10299            project.open_local_buffer(path!("/file.rs"), cx)
10300        })
10301        .await
10302        .unwrap();
10303
10304    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10305    let (editor, cx) = cx.add_window_view(|window, cx| {
10306        build_editor_with_project(project.clone(), buffer, window, cx)
10307    });
10308    editor.update_in(cx, |editor, window, cx| {
10309        editor.set_text("one\ntwo\nthree\n", window, cx)
10310    });
10311
10312    cx.executor().start_waiting();
10313    let fake_server = fake_servers.next().await.unwrap();
10314
10315    let format = editor
10316        .update_in(cx, |editor, window, cx| {
10317            editor.perform_format(
10318                project.clone(),
10319                FormatTrigger::Manual,
10320                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10321                window,
10322                cx,
10323            )
10324        })
10325        .unwrap();
10326    fake_server
10327        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10328            assert_eq!(
10329                params.text_document.uri,
10330                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10331            );
10332            assert_eq!(params.options.tab_size, 4);
10333            Ok(Some(vec![lsp::TextEdit::new(
10334                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10335                ", ".to_string(),
10336            )]))
10337        })
10338        .next()
10339        .await;
10340    cx.executor().start_waiting();
10341    format.await;
10342    assert_eq!(
10343        editor.update(cx, |editor, cx| editor.text(cx)),
10344        "one, two\nthree\n"
10345    );
10346
10347    editor.update_in(cx, |editor, window, cx| {
10348        editor.set_text("one\ntwo\nthree\n", window, cx)
10349    });
10350    // Ensure we don't lock if formatting hangs.
10351    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10352        move |params, _| async move {
10353            assert_eq!(
10354                params.text_document.uri,
10355                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10356            );
10357            futures::future::pending::<()>().await;
10358            unreachable!()
10359        },
10360    );
10361    let format = editor
10362        .update_in(cx, |editor, window, cx| {
10363            editor.perform_format(
10364                project,
10365                FormatTrigger::Manual,
10366                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10367                window,
10368                cx,
10369            )
10370        })
10371        .unwrap();
10372    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10373    cx.executor().start_waiting();
10374    format.await;
10375    assert_eq!(
10376        editor.update(cx, |editor, cx| editor.text(cx)),
10377        "one\ntwo\nthree\n"
10378    );
10379}
10380
10381#[gpui::test]
10382async fn test_multiple_formatters(cx: &mut TestAppContext) {
10383    init_test(cx, |settings| {
10384        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10385        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10386            Formatter::LanguageServer { name: None },
10387            Formatter::CodeActions(
10388                [
10389                    ("code-action-1".into(), true),
10390                    ("code-action-2".into(), true),
10391                ]
10392                .into_iter()
10393                .collect(),
10394            ),
10395        ])))
10396    });
10397
10398    let fs = FakeFs::new(cx.executor());
10399    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10400        .await;
10401
10402    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10403    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10404    language_registry.add(rust_lang());
10405
10406    let mut fake_servers = language_registry.register_fake_lsp(
10407        "Rust",
10408        FakeLspAdapter {
10409            capabilities: lsp::ServerCapabilities {
10410                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10411                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10412                    commands: vec!["the-command-for-code-action-1".into()],
10413                    ..Default::default()
10414                }),
10415                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10416                ..Default::default()
10417            },
10418            ..Default::default()
10419        },
10420    );
10421
10422    let buffer = project
10423        .update(cx, |project, cx| {
10424            project.open_local_buffer(path!("/file.rs"), cx)
10425        })
10426        .await
10427        .unwrap();
10428
10429    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10430    let (editor, cx) = cx.add_window_view(|window, cx| {
10431        build_editor_with_project(project.clone(), buffer, window, cx)
10432    });
10433
10434    cx.executor().start_waiting();
10435
10436    let fake_server = fake_servers.next().await.unwrap();
10437    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10438        move |_params, _| async move {
10439            Ok(Some(vec![lsp::TextEdit::new(
10440                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10441                "applied-formatting\n".to_string(),
10442            )]))
10443        },
10444    );
10445    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10446        move |params, _| async move {
10447            assert_eq!(
10448                params.context.only,
10449                Some(vec!["code-action-1".into(), "code-action-2".into()])
10450            );
10451            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10452            Ok(Some(vec![
10453                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10454                    kind: Some("code-action-1".into()),
10455                    edit: Some(lsp::WorkspaceEdit::new(
10456                        [(
10457                            uri.clone(),
10458                            vec![lsp::TextEdit::new(
10459                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10460                                "applied-code-action-1-edit\n".to_string(),
10461                            )],
10462                        )]
10463                        .into_iter()
10464                        .collect(),
10465                    )),
10466                    command: Some(lsp::Command {
10467                        command: "the-command-for-code-action-1".into(),
10468                        ..Default::default()
10469                    }),
10470                    ..Default::default()
10471                }),
10472                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10473                    kind: Some("code-action-2".into()),
10474                    edit: Some(lsp::WorkspaceEdit::new(
10475                        [(
10476                            uri.clone(),
10477                            vec![lsp::TextEdit::new(
10478                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10479                                "applied-code-action-2-edit\n".to_string(),
10480                            )],
10481                        )]
10482                        .into_iter()
10483                        .collect(),
10484                    )),
10485                    ..Default::default()
10486                }),
10487            ]))
10488        },
10489    );
10490
10491    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10492        move |params, _| async move { Ok(params) }
10493    });
10494
10495    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10496    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10497        let fake = fake_server.clone();
10498        let lock = command_lock.clone();
10499        move |params, _| {
10500            assert_eq!(params.command, "the-command-for-code-action-1");
10501            let fake = fake.clone();
10502            let lock = lock.clone();
10503            async move {
10504                lock.lock().await;
10505                fake.server
10506                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10507                        label: None,
10508                        edit: lsp::WorkspaceEdit {
10509                            changes: Some(
10510                                [(
10511                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10512                                    vec![lsp::TextEdit {
10513                                        range: lsp::Range::new(
10514                                            lsp::Position::new(0, 0),
10515                                            lsp::Position::new(0, 0),
10516                                        ),
10517                                        new_text: "applied-code-action-1-command\n".into(),
10518                                    }],
10519                                )]
10520                                .into_iter()
10521                                .collect(),
10522                            ),
10523                            ..Default::default()
10524                        },
10525                    })
10526                    .await
10527                    .into_response()
10528                    .unwrap();
10529                Ok(Some(json!(null)))
10530            }
10531        }
10532    });
10533
10534    cx.executor().start_waiting();
10535    editor
10536        .update_in(cx, |editor, window, cx| {
10537            editor.perform_format(
10538                project.clone(),
10539                FormatTrigger::Manual,
10540                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10541                window,
10542                cx,
10543            )
10544        })
10545        .unwrap()
10546        .await;
10547    editor.update(cx, |editor, cx| {
10548        assert_eq!(
10549            editor.text(cx),
10550            r#"
10551                applied-code-action-2-edit
10552                applied-code-action-1-command
10553                applied-code-action-1-edit
10554                applied-formatting
10555                one
10556                two
10557                three
10558            "#
10559            .unindent()
10560        );
10561    });
10562
10563    editor.update_in(cx, |editor, window, cx| {
10564        editor.undo(&Default::default(), window, cx);
10565        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10566    });
10567
10568    // Perform a manual edit while waiting for an LSP command
10569    // that's being run as part of a formatting code action.
10570    let lock_guard = command_lock.lock().await;
10571    let format = editor
10572        .update_in(cx, |editor, window, cx| {
10573            editor.perform_format(
10574                project.clone(),
10575                FormatTrigger::Manual,
10576                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10577                window,
10578                cx,
10579            )
10580        })
10581        .unwrap();
10582    cx.run_until_parked();
10583    editor.update(cx, |editor, cx| {
10584        assert_eq!(
10585            editor.text(cx),
10586            r#"
10587                applied-code-action-1-edit
10588                applied-formatting
10589                one
10590                two
10591                three
10592            "#
10593            .unindent()
10594        );
10595
10596        editor.buffer.update(cx, |buffer, cx| {
10597            let ix = buffer.len(cx);
10598            buffer.edit([(ix..ix, "edited\n")], None, cx);
10599        });
10600    });
10601
10602    // Allow the LSP command to proceed. Because the buffer was edited,
10603    // the second code action will not be run.
10604    drop(lock_guard);
10605    format.await;
10606    editor.update_in(cx, |editor, window, cx| {
10607        assert_eq!(
10608            editor.text(cx),
10609            r#"
10610                applied-code-action-1-command
10611                applied-code-action-1-edit
10612                applied-formatting
10613                one
10614                two
10615                three
10616                edited
10617            "#
10618            .unindent()
10619        );
10620
10621        // The manual edit is undone first, because it is the last thing the user did
10622        // (even though the command completed afterwards).
10623        editor.undo(&Default::default(), window, cx);
10624        assert_eq!(
10625            editor.text(cx),
10626            r#"
10627                applied-code-action-1-command
10628                applied-code-action-1-edit
10629                applied-formatting
10630                one
10631                two
10632                three
10633            "#
10634            .unindent()
10635        );
10636
10637        // All the formatting (including the command, which completed after the manual edit)
10638        // is undone together.
10639        editor.undo(&Default::default(), window, cx);
10640        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10641    });
10642}
10643
10644#[gpui::test]
10645async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10646    init_test(cx, |settings| {
10647        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10648            Formatter::LanguageServer { name: None },
10649        ])))
10650    });
10651
10652    let fs = FakeFs::new(cx.executor());
10653    fs.insert_file(path!("/file.ts"), Default::default()).await;
10654
10655    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10656
10657    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10658    language_registry.add(Arc::new(Language::new(
10659        LanguageConfig {
10660            name: "TypeScript".into(),
10661            matcher: LanguageMatcher {
10662                path_suffixes: vec!["ts".to_string()],
10663                ..Default::default()
10664            },
10665            ..LanguageConfig::default()
10666        },
10667        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10668    )));
10669    update_test_language_settings(cx, |settings| {
10670        settings.defaults.prettier = Some(PrettierSettings {
10671            allowed: true,
10672            ..PrettierSettings::default()
10673        });
10674    });
10675    let mut fake_servers = language_registry.register_fake_lsp(
10676        "TypeScript",
10677        FakeLspAdapter {
10678            capabilities: lsp::ServerCapabilities {
10679                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10680                ..Default::default()
10681            },
10682            ..Default::default()
10683        },
10684    );
10685
10686    let buffer = project
10687        .update(cx, |project, cx| {
10688            project.open_local_buffer(path!("/file.ts"), cx)
10689        })
10690        .await
10691        .unwrap();
10692
10693    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10694    let (editor, cx) = cx.add_window_view(|window, cx| {
10695        build_editor_with_project(project.clone(), buffer, window, cx)
10696    });
10697    editor.update_in(cx, |editor, window, cx| {
10698        editor.set_text(
10699            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10700            window,
10701            cx,
10702        )
10703    });
10704
10705    cx.executor().start_waiting();
10706    let fake_server = fake_servers.next().await.unwrap();
10707
10708    let format = editor
10709        .update_in(cx, |editor, window, cx| {
10710            editor.perform_code_action_kind(
10711                project.clone(),
10712                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10713                window,
10714                cx,
10715            )
10716        })
10717        .unwrap();
10718    fake_server
10719        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10720            assert_eq!(
10721                params.text_document.uri,
10722                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10723            );
10724            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10725                lsp::CodeAction {
10726                    title: "Organize Imports".to_string(),
10727                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10728                    edit: Some(lsp::WorkspaceEdit {
10729                        changes: Some(
10730                            [(
10731                                params.text_document.uri.clone(),
10732                                vec![lsp::TextEdit::new(
10733                                    lsp::Range::new(
10734                                        lsp::Position::new(1, 0),
10735                                        lsp::Position::new(2, 0),
10736                                    ),
10737                                    "".to_string(),
10738                                )],
10739                            )]
10740                            .into_iter()
10741                            .collect(),
10742                        ),
10743                        ..Default::default()
10744                    }),
10745                    ..Default::default()
10746                },
10747            )]))
10748        })
10749        .next()
10750        .await;
10751    cx.executor().start_waiting();
10752    format.await;
10753    assert_eq!(
10754        editor.update(cx, |editor, cx| editor.text(cx)),
10755        "import { a } from 'module';\n\nconst x = a;\n"
10756    );
10757
10758    editor.update_in(cx, |editor, window, cx| {
10759        editor.set_text(
10760            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10761            window,
10762            cx,
10763        )
10764    });
10765    // Ensure we don't lock if code action hangs.
10766    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10767        move |params, _| async move {
10768            assert_eq!(
10769                params.text_document.uri,
10770                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10771            );
10772            futures::future::pending::<()>().await;
10773            unreachable!()
10774        },
10775    );
10776    let format = editor
10777        .update_in(cx, |editor, window, cx| {
10778            editor.perform_code_action_kind(
10779                project,
10780                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10781                window,
10782                cx,
10783            )
10784        })
10785        .unwrap();
10786    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10787    cx.executor().start_waiting();
10788    format.await;
10789    assert_eq!(
10790        editor.update(cx, |editor, cx| editor.text(cx)),
10791        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10792    );
10793}
10794
10795#[gpui::test]
10796async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10797    init_test(cx, |_| {});
10798
10799    let mut cx = EditorLspTestContext::new_rust(
10800        lsp::ServerCapabilities {
10801            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10802            ..Default::default()
10803        },
10804        cx,
10805    )
10806    .await;
10807
10808    cx.set_state(indoc! {"
10809        one.twoˇ
10810    "});
10811
10812    // The format request takes a long time. When it completes, it inserts
10813    // a newline and an indent before the `.`
10814    cx.lsp
10815        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10816            let executor = cx.background_executor().clone();
10817            async move {
10818                executor.timer(Duration::from_millis(100)).await;
10819                Ok(Some(vec![lsp::TextEdit {
10820                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10821                    new_text: "\n    ".into(),
10822                }]))
10823            }
10824        });
10825
10826    // Submit a format request.
10827    let format_1 = cx
10828        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10829        .unwrap();
10830    cx.executor().run_until_parked();
10831
10832    // Submit a second format request.
10833    let format_2 = cx
10834        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10835        .unwrap();
10836    cx.executor().run_until_parked();
10837
10838    // Wait for both format requests to complete
10839    cx.executor().advance_clock(Duration::from_millis(200));
10840    cx.executor().start_waiting();
10841    format_1.await.unwrap();
10842    cx.executor().start_waiting();
10843    format_2.await.unwrap();
10844
10845    // The formatting edits only happens once.
10846    cx.assert_editor_state(indoc! {"
10847        one
10848            .twoˇ
10849    "});
10850}
10851
10852#[gpui::test]
10853async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10854    init_test(cx, |settings| {
10855        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10856    });
10857
10858    let mut cx = EditorLspTestContext::new_rust(
10859        lsp::ServerCapabilities {
10860            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10861            ..Default::default()
10862        },
10863        cx,
10864    )
10865    .await;
10866
10867    // Set up a buffer white some trailing whitespace and no trailing newline.
10868    cx.set_state(
10869        &[
10870            "one ",   //
10871            "twoˇ",   //
10872            "three ", //
10873            "four",   //
10874        ]
10875        .join("\n"),
10876    );
10877
10878    // Submit a format request.
10879    let format = cx
10880        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10881        .unwrap();
10882
10883    // Record which buffer changes have been sent to the language server
10884    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10885    cx.lsp
10886        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10887            let buffer_changes = buffer_changes.clone();
10888            move |params, _| {
10889                buffer_changes.lock().extend(
10890                    params
10891                        .content_changes
10892                        .into_iter()
10893                        .map(|e| (e.range.unwrap(), e.text)),
10894                );
10895            }
10896        });
10897
10898    // Handle formatting requests to the language server.
10899    cx.lsp
10900        .set_request_handler::<lsp::request::Formatting, _, _>({
10901            let buffer_changes = buffer_changes.clone();
10902            move |_, _| {
10903                // When formatting is requested, trailing whitespace has already been stripped,
10904                // and the trailing newline has already been added.
10905                assert_eq!(
10906                    &buffer_changes.lock()[1..],
10907                    &[
10908                        (
10909                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10910                            "".into()
10911                        ),
10912                        (
10913                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10914                            "".into()
10915                        ),
10916                        (
10917                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10918                            "\n".into()
10919                        ),
10920                    ]
10921                );
10922
10923                // Insert blank lines between each line of the buffer.
10924                async move {
10925                    Ok(Some(vec![
10926                        lsp::TextEdit {
10927                            range: lsp::Range::new(
10928                                lsp::Position::new(1, 0),
10929                                lsp::Position::new(1, 0),
10930                            ),
10931                            new_text: "\n".into(),
10932                        },
10933                        lsp::TextEdit {
10934                            range: lsp::Range::new(
10935                                lsp::Position::new(2, 0),
10936                                lsp::Position::new(2, 0),
10937                            ),
10938                            new_text: "\n".into(),
10939                        },
10940                    ]))
10941                }
10942            }
10943        });
10944
10945    // After formatting the buffer, the trailing whitespace is stripped,
10946    // a newline is appended, and the edits provided by the language server
10947    // have been applied.
10948    format.await.unwrap();
10949    cx.assert_editor_state(
10950        &[
10951            "one",   //
10952            "",      //
10953            "twoˇ",  //
10954            "",      //
10955            "three", //
10956            "four",  //
10957            "",      //
10958        ]
10959        .join("\n"),
10960    );
10961
10962    // Undoing the formatting undoes the trailing whitespace removal, the
10963    // trailing newline, and the LSP edits.
10964    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10965    cx.assert_editor_state(
10966        &[
10967            "one ",   //
10968            "twoˇ",   //
10969            "three ", //
10970            "four",   //
10971        ]
10972        .join("\n"),
10973    );
10974}
10975
10976#[gpui::test]
10977async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10978    cx: &mut TestAppContext,
10979) {
10980    init_test(cx, |_| {});
10981
10982    cx.update(|cx| {
10983        cx.update_global::<SettingsStore, _>(|settings, cx| {
10984            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10985                settings.auto_signature_help = Some(true);
10986            });
10987        });
10988    });
10989
10990    let mut cx = EditorLspTestContext::new_rust(
10991        lsp::ServerCapabilities {
10992            signature_help_provider: Some(lsp::SignatureHelpOptions {
10993                ..Default::default()
10994            }),
10995            ..Default::default()
10996        },
10997        cx,
10998    )
10999    .await;
11000
11001    let language = Language::new(
11002        LanguageConfig {
11003            name: "Rust".into(),
11004            brackets: BracketPairConfig {
11005                pairs: vec![
11006                    BracketPair {
11007                        start: "{".to_string(),
11008                        end: "}".to_string(),
11009                        close: true,
11010                        surround: true,
11011                        newline: true,
11012                    },
11013                    BracketPair {
11014                        start: "(".to_string(),
11015                        end: ")".to_string(),
11016                        close: true,
11017                        surround: true,
11018                        newline: true,
11019                    },
11020                    BracketPair {
11021                        start: "/*".to_string(),
11022                        end: " */".to_string(),
11023                        close: true,
11024                        surround: true,
11025                        newline: true,
11026                    },
11027                    BracketPair {
11028                        start: "[".to_string(),
11029                        end: "]".to_string(),
11030                        close: false,
11031                        surround: false,
11032                        newline: true,
11033                    },
11034                    BracketPair {
11035                        start: "\"".to_string(),
11036                        end: "\"".to_string(),
11037                        close: true,
11038                        surround: true,
11039                        newline: false,
11040                    },
11041                    BracketPair {
11042                        start: "<".to_string(),
11043                        end: ">".to_string(),
11044                        close: false,
11045                        surround: true,
11046                        newline: true,
11047                    },
11048                ],
11049                ..Default::default()
11050            },
11051            autoclose_before: "})]".to_string(),
11052            ..Default::default()
11053        },
11054        Some(tree_sitter_rust::LANGUAGE.into()),
11055    );
11056    let language = Arc::new(language);
11057
11058    cx.language_registry().add(language.clone());
11059    cx.update_buffer(|buffer, cx| {
11060        buffer.set_language(Some(language), cx);
11061    });
11062
11063    cx.set_state(
11064        &r#"
11065            fn main() {
11066                sampleˇ
11067            }
11068        "#
11069        .unindent(),
11070    );
11071
11072    cx.update_editor(|editor, window, cx| {
11073        editor.handle_input("(", window, cx);
11074    });
11075    cx.assert_editor_state(
11076        &"
11077            fn main() {
11078                sample(ˇ)
11079            }
11080        "
11081        .unindent(),
11082    );
11083
11084    let mocked_response = lsp::SignatureHelp {
11085        signatures: vec![lsp::SignatureInformation {
11086            label: "fn sample(param1: u8, param2: u8)".to_string(),
11087            documentation: None,
11088            parameters: Some(vec![
11089                lsp::ParameterInformation {
11090                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11091                    documentation: None,
11092                },
11093                lsp::ParameterInformation {
11094                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11095                    documentation: None,
11096                },
11097            ]),
11098            active_parameter: None,
11099        }],
11100        active_signature: Some(0),
11101        active_parameter: Some(0),
11102    };
11103    handle_signature_help_request(&mut cx, mocked_response).await;
11104
11105    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11106        .await;
11107
11108    cx.editor(|editor, _, _| {
11109        let signature_help_state = editor.signature_help_state.popover().cloned();
11110        let signature = signature_help_state.unwrap();
11111        assert_eq!(
11112            signature.signatures[signature.current_signature].label,
11113            "fn sample(param1: u8, param2: u8)"
11114        );
11115    });
11116}
11117
11118#[gpui::test]
11119async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11120    init_test(cx, |_| {});
11121
11122    cx.update(|cx| {
11123        cx.update_global::<SettingsStore, _>(|settings, cx| {
11124            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11125                settings.auto_signature_help = Some(false);
11126                settings.show_signature_help_after_edits = Some(false);
11127            });
11128        });
11129    });
11130
11131    let mut cx = EditorLspTestContext::new_rust(
11132        lsp::ServerCapabilities {
11133            signature_help_provider: Some(lsp::SignatureHelpOptions {
11134                ..Default::default()
11135            }),
11136            ..Default::default()
11137        },
11138        cx,
11139    )
11140    .await;
11141
11142    let language = Language::new(
11143        LanguageConfig {
11144            name: "Rust".into(),
11145            brackets: BracketPairConfig {
11146                pairs: vec![
11147                    BracketPair {
11148                        start: "{".to_string(),
11149                        end: "}".to_string(),
11150                        close: true,
11151                        surround: true,
11152                        newline: true,
11153                    },
11154                    BracketPair {
11155                        start: "(".to_string(),
11156                        end: ")".to_string(),
11157                        close: true,
11158                        surround: true,
11159                        newline: true,
11160                    },
11161                    BracketPair {
11162                        start: "/*".to_string(),
11163                        end: " */".to_string(),
11164                        close: true,
11165                        surround: true,
11166                        newline: true,
11167                    },
11168                    BracketPair {
11169                        start: "[".to_string(),
11170                        end: "]".to_string(),
11171                        close: false,
11172                        surround: false,
11173                        newline: true,
11174                    },
11175                    BracketPair {
11176                        start: "\"".to_string(),
11177                        end: "\"".to_string(),
11178                        close: true,
11179                        surround: true,
11180                        newline: false,
11181                    },
11182                    BracketPair {
11183                        start: "<".to_string(),
11184                        end: ">".to_string(),
11185                        close: false,
11186                        surround: true,
11187                        newline: true,
11188                    },
11189                ],
11190                ..Default::default()
11191            },
11192            autoclose_before: "})]".to_string(),
11193            ..Default::default()
11194        },
11195        Some(tree_sitter_rust::LANGUAGE.into()),
11196    );
11197    let language = Arc::new(language);
11198
11199    cx.language_registry().add(language.clone());
11200    cx.update_buffer(|buffer, cx| {
11201        buffer.set_language(Some(language), cx);
11202    });
11203
11204    // Ensure that signature_help is not called when no signature help is enabled.
11205    cx.set_state(
11206        &r#"
11207            fn main() {
11208                sampleˇ
11209            }
11210        "#
11211        .unindent(),
11212    );
11213    cx.update_editor(|editor, window, cx| {
11214        editor.handle_input("(", window, cx);
11215    });
11216    cx.assert_editor_state(
11217        &"
11218            fn main() {
11219                sample(ˇ)
11220            }
11221        "
11222        .unindent(),
11223    );
11224    cx.editor(|editor, _, _| {
11225        assert!(editor.signature_help_state.task().is_none());
11226    });
11227
11228    let mocked_response = lsp::SignatureHelp {
11229        signatures: vec![lsp::SignatureInformation {
11230            label: "fn sample(param1: u8, param2: u8)".to_string(),
11231            documentation: None,
11232            parameters: Some(vec![
11233                lsp::ParameterInformation {
11234                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11235                    documentation: None,
11236                },
11237                lsp::ParameterInformation {
11238                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11239                    documentation: None,
11240                },
11241            ]),
11242            active_parameter: None,
11243        }],
11244        active_signature: Some(0),
11245        active_parameter: Some(0),
11246    };
11247
11248    // Ensure that signature_help is called when enabled afte edits
11249    cx.update(|_, cx| {
11250        cx.update_global::<SettingsStore, _>(|settings, cx| {
11251            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11252                settings.auto_signature_help = Some(false);
11253                settings.show_signature_help_after_edits = Some(true);
11254            });
11255        });
11256    });
11257    cx.set_state(
11258        &r#"
11259            fn main() {
11260                sampleˇ
11261            }
11262        "#
11263        .unindent(),
11264    );
11265    cx.update_editor(|editor, window, cx| {
11266        editor.handle_input("(", window, cx);
11267    });
11268    cx.assert_editor_state(
11269        &"
11270            fn main() {
11271                sample(ˇ)
11272            }
11273        "
11274        .unindent(),
11275    );
11276    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11277    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11278        .await;
11279    cx.update_editor(|editor, _, _| {
11280        let signature_help_state = editor.signature_help_state.popover().cloned();
11281        assert!(signature_help_state.is_some());
11282        let signature = signature_help_state.unwrap();
11283        assert_eq!(
11284            signature.signatures[signature.current_signature].label,
11285            "fn sample(param1: u8, param2: u8)"
11286        );
11287        editor.signature_help_state = SignatureHelpState::default();
11288    });
11289
11290    // Ensure that signature_help is called when auto signature help override is enabled
11291    cx.update(|_, cx| {
11292        cx.update_global::<SettingsStore, _>(|settings, cx| {
11293            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11294                settings.auto_signature_help = Some(true);
11295                settings.show_signature_help_after_edits = Some(false);
11296            });
11297        });
11298    });
11299    cx.set_state(
11300        &r#"
11301            fn main() {
11302                sampleˇ
11303            }
11304        "#
11305        .unindent(),
11306    );
11307    cx.update_editor(|editor, window, cx| {
11308        editor.handle_input("(", window, cx);
11309    });
11310    cx.assert_editor_state(
11311        &"
11312            fn main() {
11313                sample(ˇ)
11314            }
11315        "
11316        .unindent(),
11317    );
11318    handle_signature_help_request(&mut cx, mocked_response).await;
11319    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11320        .await;
11321    cx.editor(|editor, _, _| {
11322        let signature_help_state = editor.signature_help_state.popover().cloned();
11323        assert!(signature_help_state.is_some());
11324        let signature = signature_help_state.unwrap();
11325        assert_eq!(
11326            signature.signatures[signature.current_signature].label,
11327            "fn sample(param1: u8, param2: u8)"
11328        );
11329    });
11330}
11331
11332#[gpui::test]
11333async fn test_signature_help(cx: &mut TestAppContext) {
11334    init_test(cx, |_| {});
11335    cx.update(|cx| {
11336        cx.update_global::<SettingsStore, _>(|settings, cx| {
11337            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11338                settings.auto_signature_help = Some(true);
11339            });
11340        });
11341    });
11342
11343    let mut cx = EditorLspTestContext::new_rust(
11344        lsp::ServerCapabilities {
11345            signature_help_provider: Some(lsp::SignatureHelpOptions {
11346                ..Default::default()
11347            }),
11348            ..Default::default()
11349        },
11350        cx,
11351    )
11352    .await;
11353
11354    // A test that directly calls `show_signature_help`
11355    cx.update_editor(|editor, window, cx| {
11356        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11357    });
11358
11359    let mocked_response = lsp::SignatureHelp {
11360        signatures: vec![lsp::SignatureInformation {
11361            label: "fn sample(param1: u8, param2: u8)".to_string(),
11362            documentation: None,
11363            parameters: Some(vec![
11364                lsp::ParameterInformation {
11365                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11366                    documentation: None,
11367                },
11368                lsp::ParameterInformation {
11369                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11370                    documentation: None,
11371                },
11372            ]),
11373            active_parameter: None,
11374        }],
11375        active_signature: Some(0),
11376        active_parameter: Some(0),
11377    };
11378    handle_signature_help_request(&mut cx, mocked_response).await;
11379
11380    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11381        .await;
11382
11383    cx.editor(|editor, _, _| {
11384        let signature_help_state = editor.signature_help_state.popover().cloned();
11385        assert!(signature_help_state.is_some());
11386        let signature = signature_help_state.unwrap();
11387        assert_eq!(
11388            signature.signatures[signature.current_signature].label,
11389            "fn sample(param1: u8, param2: u8)"
11390        );
11391    });
11392
11393    // When exiting outside from inside the brackets, `signature_help` is closed.
11394    cx.set_state(indoc! {"
11395        fn main() {
11396            sample(ˇ);
11397        }
11398
11399        fn sample(param1: u8, param2: u8) {}
11400    "});
11401
11402    cx.update_editor(|editor, window, cx| {
11403        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11404            s.select_ranges([0..0])
11405        });
11406    });
11407
11408    let mocked_response = lsp::SignatureHelp {
11409        signatures: Vec::new(),
11410        active_signature: None,
11411        active_parameter: None,
11412    };
11413    handle_signature_help_request(&mut cx, mocked_response).await;
11414
11415    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11416        .await;
11417
11418    cx.editor(|editor, _, _| {
11419        assert!(!editor.signature_help_state.is_shown());
11420    });
11421
11422    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11423    cx.set_state(indoc! {"
11424        fn main() {
11425            sample(ˇ);
11426        }
11427
11428        fn sample(param1: u8, param2: u8) {}
11429    "});
11430
11431    let mocked_response = lsp::SignatureHelp {
11432        signatures: vec![lsp::SignatureInformation {
11433            label: "fn sample(param1: u8, param2: u8)".to_string(),
11434            documentation: None,
11435            parameters: Some(vec![
11436                lsp::ParameterInformation {
11437                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11438                    documentation: None,
11439                },
11440                lsp::ParameterInformation {
11441                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11442                    documentation: None,
11443                },
11444            ]),
11445            active_parameter: None,
11446        }],
11447        active_signature: Some(0),
11448        active_parameter: Some(0),
11449    };
11450    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11451    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11452        .await;
11453    cx.editor(|editor, _, _| {
11454        assert!(editor.signature_help_state.is_shown());
11455    });
11456
11457    // Restore the popover with more parameter input
11458    cx.set_state(indoc! {"
11459        fn main() {
11460            sample(param1, param2ˇ);
11461        }
11462
11463        fn sample(param1: u8, param2: u8) {}
11464    "});
11465
11466    let mocked_response = lsp::SignatureHelp {
11467        signatures: vec![lsp::SignatureInformation {
11468            label: "fn sample(param1: u8, param2: u8)".to_string(),
11469            documentation: None,
11470            parameters: Some(vec![
11471                lsp::ParameterInformation {
11472                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11473                    documentation: None,
11474                },
11475                lsp::ParameterInformation {
11476                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11477                    documentation: None,
11478                },
11479            ]),
11480            active_parameter: None,
11481        }],
11482        active_signature: Some(0),
11483        active_parameter: Some(1),
11484    };
11485    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11486    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11487        .await;
11488
11489    // When selecting a range, the popover is gone.
11490    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11491    cx.update_editor(|editor, window, cx| {
11492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11493            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11494        })
11495    });
11496    cx.assert_editor_state(indoc! {"
11497        fn main() {
11498            sample(param1, «ˇparam2»);
11499        }
11500
11501        fn sample(param1: u8, param2: u8) {}
11502    "});
11503    cx.editor(|editor, _, _| {
11504        assert!(!editor.signature_help_state.is_shown());
11505    });
11506
11507    // When unselecting again, the popover is back if within the brackets.
11508    cx.update_editor(|editor, window, cx| {
11509        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11510            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11511        })
11512    });
11513    cx.assert_editor_state(indoc! {"
11514        fn main() {
11515            sample(param1, ˇparam2);
11516        }
11517
11518        fn sample(param1: u8, param2: u8) {}
11519    "});
11520    handle_signature_help_request(&mut cx, mocked_response).await;
11521    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11522        .await;
11523    cx.editor(|editor, _, _| {
11524        assert!(editor.signature_help_state.is_shown());
11525    });
11526
11527    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11528    cx.update_editor(|editor, window, cx| {
11529        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11530            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11531            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11532        })
11533    });
11534    cx.assert_editor_state(indoc! {"
11535        fn main() {
11536            sample(param1, ˇparam2);
11537        }
11538
11539        fn sample(param1: u8, param2: u8) {}
11540    "});
11541
11542    let mocked_response = lsp::SignatureHelp {
11543        signatures: vec![lsp::SignatureInformation {
11544            label: "fn sample(param1: u8, param2: u8)".to_string(),
11545            documentation: None,
11546            parameters: Some(vec![
11547                lsp::ParameterInformation {
11548                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11549                    documentation: None,
11550                },
11551                lsp::ParameterInformation {
11552                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11553                    documentation: None,
11554                },
11555            ]),
11556            active_parameter: None,
11557        }],
11558        active_signature: Some(0),
11559        active_parameter: Some(1),
11560    };
11561    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11562    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11563        .await;
11564    cx.update_editor(|editor, _, cx| {
11565        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11566    });
11567    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11568        .await;
11569    cx.update_editor(|editor, window, cx| {
11570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11571            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11572        })
11573    });
11574    cx.assert_editor_state(indoc! {"
11575        fn main() {
11576            sample(param1, «ˇparam2»);
11577        }
11578
11579        fn sample(param1: u8, param2: u8) {}
11580    "});
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    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11594        .await;
11595}
11596
11597#[gpui::test]
11598async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11599    init_test(cx, |_| {});
11600
11601    let mut cx = EditorLspTestContext::new_rust(
11602        lsp::ServerCapabilities {
11603            signature_help_provider: Some(lsp::SignatureHelpOptions {
11604                ..Default::default()
11605            }),
11606            ..Default::default()
11607        },
11608        cx,
11609    )
11610    .await;
11611
11612    cx.set_state(indoc! {"
11613        fn main() {
11614            overloadedˇ
11615        }
11616    "});
11617
11618    cx.update_editor(|editor, window, cx| {
11619        editor.handle_input("(", window, cx);
11620        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11621    });
11622
11623    // Mock response with 3 signatures
11624    let mocked_response = lsp::SignatureHelp {
11625        signatures: vec![
11626            lsp::SignatureInformation {
11627                label: "fn overloaded(x: i32)".to_string(),
11628                documentation: None,
11629                parameters: Some(vec![lsp::ParameterInformation {
11630                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11631                    documentation: None,
11632                }]),
11633                active_parameter: None,
11634            },
11635            lsp::SignatureInformation {
11636                label: "fn overloaded(x: i32, y: i32)".to_string(),
11637                documentation: None,
11638                parameters: Some(vec![
11639                    lsp::ParameterInformation {
11640                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11641                        documentation: None,
11642                    },
11643                    lsp::ParameterInformation {
11644                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11645                        documentation: None,
11646                    },
11647                ]),
11648                active_parameter: None,
11649            },
11650            lsp::SignatureInformation {
11651                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11652                documentation: None,
11653                parameters: Some(vec![
11654                    lsp::ParameterInformation {
11655                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11656                        documentation: None,
11657                    },
11658                    lsp::ParameterInformation {
11659                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11660                        documentation: None,
11661                    },
11662                    lsp::ParameterInformation {
11663                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11664                        documentation: None,
11665                    },
11666                ]),
11667                active_parameter: None,
11668            },
11669        ],
11670        active_signature: Some(1),
11671        active_parameter: Some(0),
11672    };
11673    handle_signature_help_request(&mut cx, mocked_response).await;
11674
11675    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11676        .await;
11677
11678    // Verify we have multiple signatures and the right one is selected
11679    cx.editor(|editor, _, _| {
11680        let popover = editor.signature_help_state.popover().cloned().unwrap();
11681        assert_eq!(popover.signatures.len(), 3);
11682        // active_signature was 1, so that should be the current
11683        assert_eq!(popover.current_signature, 1);
11684        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11685        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11686        assert_eq!(
11687            popover.signatures[2].label,
11688            "fn overloaded(x: i32, y: i32, z: i32)"
11689        );
11690    });
11691
11692    // Test navigation functionality
11693    cx.update_editor(|editor, window, cx| {
11694        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11695    });
11696
11697    cx.editor(|editor, _, _| {
11698        let popover = editor.signature_help_state.popover().cloned().unwrap();
11699        assert_eq!(popover.current_signature, 2);
11700    });
11701
11702    // Test wrap around
11703    cx.update_editor(|editor, window, cx| {
11704        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11705    });
11706
11707    cx.editor(|editor, _, _| {
11708        let popover = editor.signature_help_state.popover().cloned().unwrap();
11709        assert_eq!(popover.current_signature, 0);
11710    });
11711
11712    // Test previous navigation
11713    cx.update_editor(|editor, window, cx| {
11714        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11715    });
11716
11717    cx.editor(|editor, _, _| {
11718        let popover = editor.signature_help_state.popover().cloned().unwrap();
11719        assert_eq!(popover.current_signature, 2);
11720    });
11721}
11722
11723#[gpui::test]
11724async fn test_completion_mode(cx: &mut TestAppContext) {
11725    init_test(cx, |_| {});
11726    let mut cx = EditorLspTestContext::new_rust(
11727        lsp::ServerCapabilities {
11728            completion_provider: Some(lsp::CompletionOptions {
11729                resolve_provider: Some(true),
11730                ..Default::default()
11731            }),
11732            ..Default::default()
11733        },
11734        cx,
11735    )
11736    .await;
11737
11738    struct Run {
11739        run_description: &'static str,
11740        initial_state: String,
11741        buffer_marked_text: String,
11742        completion_label: &'static str,
11743        completion_text: &'static str,
11744        expected_with_insert_mode: String,
11745        expected_with_replace_mode: String,
11746        expected_with_replace_subsequence_mode: String,
11747        expected_with_replace_suffix_mode: String,
11748    }
11749
11750    let runs = [
11751        Run {
11752            run_description: "Start of word matches completion text",
11753            initial_state: "before ediˇ after".into(),
11754            buffer_marked_text: "before <edi|> after".into(),
11755            completion_label: "editor",
11756            completion_text: "editor",
11757            expected_with_insert_mode: "before editorˇ after".into(),
11758            expected_with_replace_mode: "before editorˇ after".into(),
11759            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11760            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11761        },
11762        Run {
11763            run_description: "Accept same text at the middle of the word",
11764            initial_state: "before ediˇtor after".into(),
11765            buffer_marked_text: "before <edi|tor> after".into(),
11766            completion_label: "editor",
11767            completion_text: "editor",
11768            expected_with_insert_mode: "before editorˇtor after".into(),
11769            expected_with_replace_mode: "before editorˇ after".into(),
11770            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11771            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11772        },
11773        Run {
11774            run_description: "End of word matches completion text -- cursor at end",
11775            initial_state: "before torˇ after".into(),
11776            buffer_marked_text: "before <tor|> after".into(),
11777            completion_label: "editor",
11778            completion_text: "editor",
11779            expected_with_insert_mode: "before editorˇ after".into(),
11780            expected_with_replace_mode: "before editorˇ after".into(),
11781            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11782            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11783        },
11784        Run {
11785            run_description: "End of word matches completion text -- cursor at start",
11786            initial_state: "before ˇtor after".into(),
11787            buffer_marked_text: "before <|tor> after".into(),
11788            completion_label: "editor",
11789            completion_text: "editor",
11790            expected_with_insert_mode: "before editorˇtor after".into(),
11791            expected_with_replace_mode: "before editorˇ after".into(),
11792            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11793            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11794        },
11795        Run {
11796            run_description: "Prepend text containing whitespace",
11797            initial_state: "pˇfield: bool".into(),
11798            buffer_marked_text: "<p|field>: bool".into(),
11799            completion_label: "pub ",
11800            completion_text: "pub ",
11801            expected_with_insert_mode: "pub ˇfield: bool".into(),
11802            expected_with_replace_mode: "pub ˇ: bool".into(),
11803            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11804            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11805        },
11806        Run {
11807            run_description: "Add element to start of list",
11808            initial_state: "[element_ˇelement_2]".into(),
11809            buffer_marked_text: "[<element_|element_2>]".into(),
11810            completion_label: "element_1",
11811            completion_text: "element_1",
11812            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11813            expected_with_replace_mode: "[element_1ˇ]".into(),
11814            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11815            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11816        },
11817        Run {
11818            run_description: "Add element to start of list -- first and second elements are equal",
11819            initial_state: "[elˇelement]".into(),
11820            buffer_marked_text: "[<el|element>]".into(),
11821            completion_label: "element",
11822            completion_text: "element",
11823            expected_with_insert_mode: "[elementˇelement]".into(),
11824            expected_with_replace_mode: "[elementˇ]".into(),
11825            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11826            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11827        },
11828        Run {
11829            run_description: "Ends with matching suffix",
11830            initial_state: "SubˇError".into(),
11831            buffer_marked_text: "<Sub|Error>".into(),
11832            completion_label: "SubscriptionError",
11833            completion_text: "SubscriptionError",
11834            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11835            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11836            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11837            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11838        },
11839        Run {
11840            run_description: "Suffix is a subsequence -- contiguous",
11841            initial_state: "SubˇErr".into(),
11842            buffer_marked_text: "<Sub|Err>".into(),
11843            completion_label: "SubscriptionError",
11844            completion_text: "SubscriptionError",
11845            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11846            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11847            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11848            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11849        },
11850        Run {
11851            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11852            initial_state: "Suˇscrirr".into(),
11853            buffer_marked_text: "<Su|scrirr>".into(),
11854            completion_label: "SubscriptionError",
11855            completion_text: "SubscriptionError",
11856            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11857            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11858            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11859            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11860        },
11861        Run {
11862            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11863            initial_state: "foo(indˇix)".into(),
11864            buffer_marked_text: "foo(<ind|ix>)".into(),
11865            completion_label: "node_index",
11866            completion_text: "node_index",
11867            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11868            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11869            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11870            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11871        },
11872        Run {
11873            run_description: "Replace range ends before cursor - should extend to cursor",
11874            initial_state: "before editˇo after".into(),
11875            buffer_marked_text: "before <{ed}>it|o after".into(),
11876            completion_label: "editor",
11877            completion_text: "editor",
11878            expected_with_insert_mode: "before editorˇo after".into(),
11879            expected_with_replace_mode: "before editorˇo after".into(),
11880            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11881            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11882        },
11883        Run {
11884            run_description: "Uses label for suffix matching",
11885            initial_state: "before ediˇtor after".into(),
11886            buffer_marked_text: "before <edi|tor> after".into(),
11887            completion_label: "editor",
11888            completion_text: "editor()",
11889            expected_with_insert_mode: "before editor()ˇtor after".into(),
11890            expected_with_replace_mode: "before editor()ˇ after".into(),
11891            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11892            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11893        },
11894        Run {
11895            run_description: "Case insensitive subsequence and suffix matching",
11896            initial_state: "before EDiˇtoR after".into(),
11897            buffer_marked_text: "before <EDi|toR> after".into(),
11898            completion_label: "editor",
11899            completion_text: "editor",
11900            expected_with_insert_mode: "before editorˇtoR after".into(),
11901            expected_with_replace_mode: "before editorˇ after".into(),
11902            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11903            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11904        },
11905    ];
11906
11907    for run in runs {
11908        let run_variations = [
11909            (LspInsertMode::Insert, run.expected_with_insert_mode),
11910            (LspInsertMode::Replace, run.expected_with_replace_mode),
11911            (
11912                LspInsertMode::ReplaceSubsequence,
11913                run.expected_with_replace_subsequence_mode,
11914            ),
11915            (
11916                LspInsertMode::ReplaceSuffix,
11917                run.expected_with_replace_suffix_mode,
11918            ),
11919        ];
11920
11921        for (lsp_insert_mode, expected_text) in run_variations {
11922            eprintln!(
11923                "run = {:?}, mode = {lsp_insert_mode:.?}",
11924                run.run_description,
11925            );
11926
11927            update_test_language_settings(&mut cx, |settings| {
11928                settings.defaults.completions = Some(CompletionSettings {
11929                    lsp_insert_mode,
11930                    words: WordsCompletionMode::Disabled,
11931                    lsp: true,
11932                    lsp_fetch_timeout_ms: 0,
11933                });
11934            });
11935
11936            cx.set_state(&run.initial_state);
11937            cx.update_editor(|editor, window, cx| {
11938                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11939            });
11940
11941            let counter = Arc::new(AtomicUsize::new(0));
11942            handle_completion_request_with_insert_and_replace(
11943                &mut cx,
11944                &run.buffer_marked_text,
11945                vec![(run.completion_label, run.completion_text)],
11946                counter.clone(),
11947            )
11948            .await;
11949            cx.condition(|editor, _| editor.context_menu_visible())
11950                .await;
11951            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11952
11953            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11954                editor
11955                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11956                    .unwrap()
11957            });
11958            cx.assert_editor_state(&expected_text);
11959            handle_resolve_completion_request(&mut cx, None).await;
11960            apply_additional_edits.await.unwrap();
11961        }
11962    }
11963}
11964
11965#[gpui::test]
11966async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11967    init_test(cx, |_| {});
11968    let mut cx = EditorLspTestContext::new_rust(
11969        lsp::ServerCapabilities {
11970            completion_provider: Some(lsp::CompletionOptions {
11971                resolve_provider: Some(true),
11972                ..Default::default()
11973            }),
11974            ..Default::default()
11975        },
11976        cx,
11977    )
11978    .await;
11979
11980    let initial_state = "SubˇError";
11981    let buffer_marked_text = "<Sub|Error>";
11982    let completion_text = "SubscriptionError";
11983    let expected_with_insert_mode = "SubscriptionErrorˇError";
11984    let expected_with_replace_mode = "SubscriptionErrorˇ";
11985
11986    update_test_language_settings(&mut cx, |settings| {
11987        settings.defaults.completions = Some(CompletionSettings {
11988            words: WordsCompletionMode::Disabled,
11989            // set the opposite here to ensure that the action is overriding the default behavior
11990            lsp_insert_mode: LspInsertMode::Insert,
11991            lsp: true,
11992            lsp_fetch_timeout_ms: 0,
11993        });
11994    });
11995
11996    cx.set_state(initial_state);
11997    cx.update_editor(|editor, window, cx| {
11998        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11999    });
12000
12001    let counter = Arc::new(AtomicUsize::new(0));
12002    handle_completion_request_with_insert_and_replace(
12003        &mut cx,
12004        &buffer_marked_text,
12005        vec![(completion_text, completion_text)],
12006        counter.clone(),
12007    )
12008    .await;
12009    cx.condition(|editor, _| editor.context_menu_visible())
12010        .await;
12011    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12012
12013    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12014        editor
12015            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12016            .unwrap()
12017    });
12018    cx.assert_editor_state(&expected_with_replace_mode);
12019    handle_resolve_completion_request(&mut cx, None).await;
12020    apply_additional_edits.await.unwrap();
12021
12022    update_test_language_settings(&mut cx, |settings| {
12023        settings.defaults.completions = Some(CompletionSettings {
12024            words: WordsCompletionMode::Disabled,
12025            // set the opposite here to ensure that the action is overriding the default behavior
12026            lsp_insert_mode: LspInsertMode::Replace,
12027            lsp: true,
12028            lsp_fetch_timeout_ms: 0,
12029        });
12030    });
12031
12032    cx.set_state(initial_state);
12033    cx.update_editor(|editor, window, cx| {
12034        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12035    });
12036    handle_completion_request_with_insert_and_replace(
12037        &mut cx,
12038        &buffer_marked_text,
12039        vec![(completion_text, completion_text)],
12040        counter.clone(),
12041    )
12042    .await;
12043    cx.condition(|editor, _| editor.context_menu_visible())
12044        .await;
12045    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12046
12047    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12048        editor
12049            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12050            .unwrap()
12051    });
12052    cx.assert_editor_state(&expected_with_insert_mode);
12053    handle_resolve_completion_request(&mut cx, None).await;
12054    apply_additional_edits.await.unwrap();
12055}
12056
12057#[gpui::test]
12058async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12059    init_test(cx, |_| {});
12060    let mut cx = EditorLspTestContext::new_rust(
12061        lsp::ServerCapabilities {
12062            completion_provider: Some(lsp::CompletionOptions {
12063                resolve_provider: Some(true),
12064                ..Default::default()
12065            }),
12066            ..Default::default()
12067        },
12068        cx,
12069    )
12070    .await;
12071
12072    // scenario: surrounding text matches completion text
12073    let completion_text = "to_offset";
12074    let initial_state = indoc! {"
12075        1. buf.to_offˇsuffix
12076        2. buf.to_offˇsuf
12077        3. buf.to_offˇfix
12078        4. buf.to_offˇ
12079        5. into_offˇensive
12080        6. ˇsuffix
12081        7. let ˇ //
12082        8. aaˇzz
12083        9. buf.to_off«zzzzzˇ»suffix
12084        10. buf.«ˇzzzzz»suffix
12085        11. to_off«ˇzzzzz»
12086
12087        buf.to_offˇsuffix  // newest cursor
12088    "};
12089    let completion_marked_buffer = indoc! {"
12090        1. buf.to_offsuffix
12091        2. buf.to_offsuf
12092        3. buf.to_offfix
12093        4. buf.to_off
12094        5. into_offensive
12095        6. suffix
12096        7. let  //
12097        8. aazz
12098        9. buf.to_offzzzzzsuffix
12099        10. buf.zzzzzsuffix
12100        11. to_offzzzzz
12101
12102        buf.<to_off|suffix>  // newest cursor
12103    "};
12104    let expected = indoc! {"
12105        1. buf.to_offsetˇ
12106        2. buf.to_offsetˇsuf
12107        3. buf.to_offsetˇfix
12108        4. buf.to_offsetˇ
12109        5. into_offsetˇensive
12110        6. to_offsetˇsuffix
12111        7. let to_offsetˇ //
12112        8. aato_offsetˇzz
12113        9. buf.to_offsetˇ
12114        10. buf.to_offsetˇsuffix
12115        11. to_offsetˇ
12116
12117        buf.to_offsetˇ  // newest cursor
12118    "};
12119    cx.set_state(initial_state);
12120    cx.update_editor(|editor, window, cx| {
12121        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12122    });
12123    handle_completion_request_with_insert_and_replace(
12124        &mut cx,
12125        completion_marked_buffer,
12126        vec![(completion_text, completion_text)],
12127        Arc::new(AtomicUsize::new(0)),
12128    )
12129    .await;
12130    cx.condition(|editor, _| editor.context_menu_visible())
12131        .await;
12132    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12133        editor
12134            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12135            .unwrap()
12136    });
12137    cx.assert_editor_state(expected);
12138    handle_resolve_completion_request(&mut cx, None).await;
12139    apply_additional_edits.await.unwrap();
12140
12141    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12142    let completion_text = "foo_and_bar";
12143    let initial_state = indoc! {"
12144        1. ooanbˇ
12145        2. zooanbˇ
12146        3. ooanbˇz
12147        4. zooanbˇz
12148        5. ooanˇ
12149        6. oanbˇ
12150
12151        ooanbˇ
12152    "};
12153    let completion_marked_buffer = indoc! {"
12154        1. ooanb
12155        2. zooanb
12156        3. ooanbz
12157        4. zooanbz
12158        5. ooan
12159        6. oanb
12160
12161        <ooanb|>
12162    "};
12163    let expected = indoc! {"
12164        1. foo_and_barˇ
12165        2. zfoo_and_barˇ
12166        3. foo_and_barˇz
12167        4. zfoo_and_barˇz
12168        5. ooanfoo_and_barˇ
12169        6. oanbfoo_and_barˇ
12170
12171        foo_and_barˇ
12172    "};
12173    cx.set_state(initial_state);
12174    cx.update_editor(|editor, window, cx| {
12175        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12176    });
12177    handle_completion_request_with_insert_and_replace(
12178        &mut cx,
12179        completion_marked_buffer,
12180        vec![(completion_text, completion_text)],
12181        Arc::new(AtomicUsize::new(0)),
12182    )
12183    .await;
12184    cx.condition(|editor, _| editor.context_menu_visible())
12185        .await;
12186    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12187        editor
12188            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12189            .unwrap()
12190    });
12191    cx.assert_editor_state(expected);
12192    handle_resolve_completion_request(&mut cx, None).await;
12193    apply_additional_edits.await.unwrap();
12194
12195    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12196    // (expects the same as if it was inserted at the end)
12197    let completion_text = "foo_and_bar";
12198    let initial_state = indoc! {"
12199        1. ooˇanb
12200        2. zooˇanb
12201        3. ooˇanbz
12202        4. zooˇanbz
12203
12204        ooˇanb
12205    "};
12206    let completion_marked_buffer = indoc! {"
12207        1. ooanb
12208        2. zooanb
12209        3. ooanbz
12210        4. zooanbz
12211
12212        <oo|anb>
12213    "};
12214    let expected = indoc! {"
12215        1. foo_and_barˇ
12216        2. zfoo_and_barˇ
12217        3. foo_and_barˇz
12218        4. zfoo_and_barˇz
12219
12220        foo_and_barˇ
12221    "};
12222    cx.set_state(initial_state);
12223    cx.update_editor(|editor, window, cx| {
12224        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12225    });
12226    handle_completion_request_with_insert_and_replace(
12227        &mut cx,
12228        completion_marked_buffer,
12229        vec![(completion_text, completion_text)],
12230        Arc::new(AtomicUsize::new(0)),
12231    )
12232    .await;
12233    cx.condition(|editor, _| editor.context_menu_visible())
12234        .await;
12235    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12236        editor
12237            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12238            .unwrap()
12239    });
12240    cx.assert_editor_state(expected);
12241    handle_resolve_completion_request(&mut cx, None).await;
12242    apply_additional_edits.await.unwrap();
12243}
12244
12245// This used to crash
12246#[gpui::test]
12247async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12248    init_test(cx, |_| {});
12249
12250    let buffer_text = indoc! {"
12251        fn main() {
12252            10.satu;
12253
12254            //
12255            // separate cursors so they open in different excerpts (manually reproducible)
12256            //
12257
12258            10.satu20;
12259        }
12260    "};
12261    let multibuffer_text_with_selections = indoc! {"
12262        fn main() {
12263            10.satuˇ;
12264
12265            //
12266
12267            //
12268
12269            10.satuˇ20;
12270        }
12271    "};
12272    let expected_multibuffer = indoc! {"
12273        fn main() {
12274            10.saturating_sub()ˇ;
12275
12276            //
12277
12278            //
12279
12280            10.saturating_sub()ˇ;
12281        }
12282    "};
12283
12284    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12285    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12286
12287    let fs = FakeFs::new(cx.executor());
12288    fs.insert_tree(
12289        path!("/a"),
12290        json!({
12291            "main.rs": buffer_text,
12292        }),
12293    )
12294    .await;
12295
12296    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12297    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12298    language_registry.add(rust_lang());
12299    let mut fake_servers = language_registry.register_fake_lsp(
12300        "Rust",
12301        FakeLspAdapter {
12302            capabilities: lsp::ServerCapabilities {
12303                completion_provider: Some(lsp::CompletionOptions {
12304                    resolve_provider: None,
12305                    ..lsp::CompletionOptions::default()
12306                }),
12307                ..lsp::ServerCapabilities::default()
12308            },
12309            ..FakeLspAdapter::default()
12310        },
12311    );
12312    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12313    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12314    let buffer = project
12315        .update(cx, |project, cx| {
12316            project.open_local_buffer(path!("/a/main.rs"), cx)
12317        })
12318        .await
12319        .unwrap();
12320
12321    let multi_buffer = cx.new(|cx| {
12322        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12323        multi_buffer.push_excerpts(
12324            buffer.clone(),
12325            [ExcerptRange::new(0..first_excerpt_end)],
12326            cx,
12327        );
12328        multi_buffer.push_excerpts(
12329            buffer.clone(),
12330            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12331            cx,
12332        );
12333        multi_buffer
12334    });
12335
12336    let editor = workspace
12337        .update(cx, |_, window, cx| {
12338            cx.new(|cx| {
12339                Editor::new(
12340                    EditorMode::Full {
12341                        scale_ui_elements_with_buffer_font_size: false,
12342                        show_active_line_background: false,
12343                        sized_by_content: false,
12344                    },
12345                    multi_buffer.clone(),
12346                    Some(project.clone()),
12347                    window,
12348                    cx,
12349                )
12350            })
12351        })
12352        .unwrap();
12353
12354    let pane = workspace
12355        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12356        .unwrap();
12357    pane.update_in(cx, |pane, window, cx| {
12358        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12359    });
12360
12361    let fake_server = fake_servers.next().await.unwrap();
12362
12363    editor.update_in(cx, |editor, window, cx| {
12364        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12365            s.select_ranges([
12366                Point::new(1, 11)..Point::new(1, 11),
12367                Point::new(7, 11)..Point::new(7, 11),
12368            ])
12369        });
12370
12371        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12372    });
12373
12374    editor.update_in(cx, |editor, window, cx| {
12375        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12376    });
12377
12378    fake_server
12379        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12380            let completion_item = lsp::CompletionItem {
12381                label: "saturating_sub()".into(),
12382                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12383                    lsp::InsertReplaceEdit {
12384                        new_text: "saturating_sub()".to_owned(),
12385                        insert: lsp::Range::new(
12386                            lsp::Position::new(7, 7),
12387                            lsp::Position::new(7, 11),
12388                        ),
12389                        replace: lsp::Range::new(
12390                            lsp::Position::new(7, 7),
12391                            lsp::Position::new(7, 13),
12392                        ),
12393                    },
12394                )),
12395                ..lsp::CompletionItem::default()
12396            };
12397
12398            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12399        })
12400        .next()
12401        .await
12402        .unwrap();
12403
12404    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12405        .await;
12406
12407    editor
12408        .update_in(cx, |editor, window, cx| {
12409            editor
12410                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12411                .unwrap()
12412        })
12413        .await
12414        .unwrap();
12415
12416    editor.update(cx, |editor, cx| {
12417        assert_text_with_selections(editor, expected_multibuffer, cx);
12418    })
12419}
12420
12421#[gpui::test]
12422async fn test_completion(cx: &mut TestAppContext) {
12423    init_test(cx, |_| {});
12424
12425    let mut cx = EditorLspTestContext::new_rust(
12426        lsp::ServerCapabilities {
12427            completion_provider: Some(lsp::CompletionOptions {
12428                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12429                resolve_provider: Some(true),
12430                ..Default::default()
12431            }),
12432            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12433            ..Default::default()
12434        },
12435        cx,
12436    )
12437    .await;
12438    let counter = Arc::new(AtomicUsize::new(0));
12439
12440    cx.set_state(indoc! {"
12441        oneˇ
12442        two
12443        three
12444    "});
12445    cx.simulate_keystroke(".");
12446    handle_completion_request(
12447        indoc! {"
12448            one.|<>
12449            two
12450            three
12451        "},
12452        vec!["first_completion", "second_completion"],
12453        true,
12454        counter.clone(),
12455        &mut cx,
12456    )
12457    .await;
12458    cx.condition(|editor, _| editor.context_menu_visible())
12459        .await;
12460    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12461
12462    let _handler = handle_signature_help_request(
12463        &mut cx,
12464        lsp::SignatureHelp {
12465            signatures: vec![lsp::SignatureInformation {
12466                label: "test signature".to_string(),
12467                documentation: None,
12468                parameters: Some(vec![lsp::ParameterInformation {
12469                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12470                    documentation: None,
12471                }]),
12472                active_parameter: None,
12473            }],
12474            active_signature: None,
12475            active_parameter: None,
12476        },
12477    );
12478    cx.update_editor(|editor, window, cx| {
12479        assert!(
12480            !editor.signature_help_state.is_shown(),
12481            "No signature help was called for"
12482        );
12483        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12484    });
12485    cx.run_until_parked();
12486    cx.update_editor(|editor, _, _| {
12487        assert!(
12488            !editor.signature_help_state.is_shown(),
12489            "No signature help should be shown when completions menu is open"
12490        );
12491    });
12492
12493    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12494        editor.context_menu_next(&Default::default(), window, cx);
12495        editor
12496            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12497            .unwrap()
12498    });
12499    cx.assert_editor_state(indoc! {"
12500        one.second_completionˇ
12501        two
12502        three
12503    "});
12504
12505    handle_resolve_completion_request(
12506        &mut cx,
12507        Some(vec![
12508            (
12509                //This overlaps with the primary completion edit which is
12510                //misbehavior from the LSP spec, test that we filter it out
12511                indoc! {"
12512                    one.second_ˇcompletion
12513                    two
12514                    threeˇ
12515                "},
12516                "overlapping additional edit",
12517            ),
12518            (
12519                indoc! {"
12520                    one.second_completion
12521                    two
12522                    threeˇ
12523                "},
12524                "\nadditional edit",
12525            ),
12526        ]),
12527    )
12528    .await;
12529    apply_additional_edits.await.unwrap();
12530    cx.assert_editor_state(indoc! {"
12531        one.second_completionˇ
12532        two
12533        three
12534        additional edit
12535    "});
12536
12537    cx.set_state(indoc! {"
12538        one.second_completion
12539        twoˇ
12540        threeˇ
12541        additional edit
12542    "});
12543    cx.simulate_keystroke(" ");
12544    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12545    cx.simulate_keystroke("s");
12546    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12547
12548    cx.assert_editor_state(indoc! {"
12549        one.second_completion
12550        two sˇ
12551        three sˇ
12552        additional edit
12553    "});
12554    handle_completion_request(
12555        indoc! {"
12556            one.second_completion
12557            two s
12558            three <s|>
12559            additional edit
12560        "},
12561        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12562        true,
12563        counter.clone(),
12564        &mut cx,
12565    )
12566    .await;
12567    cx.condition(|editor, _| editor.context_menu_visible())
12568        .await;
12569    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12570
12571    cx.simulate_keystroke("i");
12572
12573    handle_completion_request(
12574        indoc! {"
12575            one.second_completion
12576            two si
12577            three <si|>
12578            additional edit
12579        "},
12580        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12581        true,
12582        counter.clone(),
12583        &mut cx,
12584    )
12585    .await;
12586    cx.condition(|editor, _| editor.context_menu_visible())
12587        .await;
12588    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12589
12590    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12591        editor
12592            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12593            .unwrap()
12594    });
12595    cx.assert_editor_state(indoc! {"
12596        one.second_completion
12597        two sixth_completionˇ
12598        three sixth_completionˇ
12599        additional edit
12600    "});
12601
12602    apply_additional_edits.await.unwrap();
12603
12604    update_test_language_settings(&mut cx, |settings| {
12605        settings.defaults.show_completions_on_input = Some(false);
12606    });
12607    cx.set_state("editorˇ");
12608    cx.simulate_keystroke(".");
12609    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12610    cx.simulate_keystrokes("c l o");
12611    cx.assert_editor_state("editor.cloˇ");
12612    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12613    cx.update_editor(|editor, window, cx| {
12614        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12615    });
12616    handle_completion_request(
12617        "editor.<clo|>",
12618        vec!["close", "clobber"],
12619        true,
12620        counter.clone(),
12621        &mut cx,
12622    )
12623    .await;
12624    cx.condition(|editor, _| editor.context_menu_visible())
12625        .await;
12626    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12627
12628    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12629        editor
12630            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12631            .unwrap()
12632    });
12633    cx.assert_editor_state("editor.clobberˇ");
12634    handle_resolve_completion_request(&mut cx, None).await;
12635    apply_additional_edits.await.unwrap();
12636}
12637
12638#[gpui::test]
12639async fn test_completion_reuse(cx: &mut TestAppContext) {
12640    init_test(cx, |_| {});
12641
12642    let mut cx = EditorLspTestContext::new_rust(
12643        lsp::ServerCapabilities {
12644            completion_provider: Some(lsp::CompletionOptions {
12645                trigger_characters: Some(vec![".".to_string()]),
12646                ..Default::default()
12647            }),
12648            ..Default::default()
12649        },
12650        cx,
12651    )
12652    .await;
12653
12654    let counter = Arc::new(AtomicUsize::new(0));
12655    cx.set_state("objˇ");
12656    cx.simulate_keystroke(".");
12657
12658    // Initial completion request returns complete results
12659    let is_incomplete = false;
12660    handle_completion_request(
12661        "obj.|<>",
12662        vec!["a", "ab", "abc"],
12663        is_incomplete,
12664        counter.clone(),
12665        &mut cx,
12666    )
12667    .await;
12668    cx.run_until_parked();
12669    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12670    cx.assert_editor_state("obj.ˇ");
12671    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12672
12673    // Type "a" - filters existing completions
12674    cx.simulate_keystroke("a");
12675    cx.run_until_parked();
12676    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12677    cx.assert_editor_state("obj.aˇ");
12678    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12679
12680    // Type "b" - filters existing completions
12681    cx.simulate_keystroke("b");
12682    cx.run_until_parked();
12683    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12684    cx.assert_editor_state("obj.abˇ");
12685    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12686
12687    // Type "c" - filters existing completions
12688    cx.simulate_keystroke("c");
12689    cx.run_until_parked();
12690    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12691    cx.assert_editor_state("obj.abcˇ");
12692    check_displayed_completions(vec!["abc"], &mut cx);
12693
12694    // Backspace to delete "c" - filters existing completions
12695    cx.update_editor(|editor, window, cx| {
12696        editor.backspace(&Backspace, window, cx);
12697    });
12698    cx.run_until_parked();
12699    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12700    cx.assert_editor_state("obj.abˇ");
12701    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12702
12703    // Moving cursor to the left dismisses menu.
12704    cx.update_editor(|editor, window, cx| {
12705        editor.move_left(&MoveLeft, window, cx);
12706    });
12707    cx.run_until_parked();
12708    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12709    cx.assert_editor_state("obj.aˇb");
12710    cx.update_editor(|editor, _, _| {
12711        assert_eq!(editor.context_menu_visible(), false);
12712    });
12713
12714    // Type "b" - new request
12715    cx.simulate_keystroke("b");
12716    let is_incomplete = false;
12717    handle_completion_request(
12718        "obj.<ab|>a",
12719        vec!["ab", "abc"],
12720        is_incomplete,
12721        counter.clone(),
12722        &mut cx,
12723    )
12724    .await;
12725    cx.run_until_parked();
12726    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12727    cx.assert_editor_state("obj.abˇb");
12728    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12729
12730    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12731    cx.update_editor(|editor, window, cx| {
12732        editor.backspace(&Backspace, window, cx);
12733    });
12734    let is_incomplete = false;
12735    handle_completion_request(
12736        "obj.<a|>b",
12737        vec!["a", "ab", "abc"],
12738        is_incomplete,
12739        counter.clone(),
12740        &mut cx,
12741    )
12742    .await;
12743    cx.run_until_parked();
12744    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12745    cx.assert_editor_state("obj.aˇb");
12746    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12747
12748    // Backspace to delete "a" - dismisses menu.
12749    cx.update_editor(|editor, window, cx| {
12750        editor.backspace(&Backspace, window, cx);
12751    });
12752    cx.run_until_parked();
12753    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12754    cx.assert_editor_state("obj.ˇb");
12755    cx.update_editor(|editor, _, _| {
12756        assert_eq!(editor.context_menu_visible(), false);
12757    });
12758}
12759
12760#[gpui::test]
12761async fn test_word_completion(cx: &mut TestAppContext) {
12762    let lsp_fetch_timeout_ms = 10;
12763    init_test(cx, |language_settings| {
12764        language_settings.defaults.completions = Some(CompletionSettings {
12765            words: WordsCompletionMode::Fallback,
12766            lsp: true,
12767            lsp_fetch_timeout_ms: 10,
12768            lsp_insert_mode: LspInsertMode::Insert,
12769        });
12770    });
12771
12772    let mut cx = EditorLspTestContext::new_rust(
12773        lsp::ServerCapabilities {
12774            completion_provider: Some(lsp::CompletionOptions {
12775                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12776                ..lsp::CompletionOptions::default()
12777            }),
12778            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12779            ..lsp::ServerCapabilities::default()
12780        },
12781        cx,
12782    )
12783    .await;
12784
12785    let throttle_completions = Arc::new(AtomicBool::new(false));
12786
12787    let lsp_throttle_completions = throttle_completions.clone();
12788    let _completion_requests_handler =
12789        cx.lsp
12790            .server
12791            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12792                let lsp_throttle_completions = lsp_throttle_completions.clone();
12793                let cx = cx.clone();
12794                async move {
12795                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12796                        cx.background_executor()
12797                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12798                            .await;
12799                    }
12800                    Ok(Some(lsp::CompletionResponse::Array(vec![
12801                        lsp::CompletionItem {
12802                            label: "first".into(),
12803                            ..lsp::CompletionItem::default()
12804                        },
12805                        lsp::CompletionItem {
12806                            label: "last".into(),
12807                            ..lsp::CompletionItem::default()
12808                        },
12809                    ])))
12810                }
12811            });
12812
12813    cx.set_state(indoc! {"
12814        oneˇ
12815        two
12816        three
12817    "});
12818    cx.simulate_keystroke(".");
12819    cx.executor().run_until_parked();
12820    cx.condition(|editor, _| editor.context_menu_visible())
12821        .await;
12822    cx.update_editor(|editor, window, cx| {
12823        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12824        {
12825            assert_eq!(
12826                completion_menu_entries(&menu),
12827                &["first", "last"],
12828                "When LSP server is fast to reply, no fallback word completions are used"
12829            );
12830        } else {
12831            panic!("expected completion menu to be open");
12832        }
12833        editor.cancel(&Cancel, window, cx);
12834    });
12835    cx.executor().run_until_parked();
12836    cx.condition(|editor, _| !editor.context_menu_visible())
12837        .await;
12838
12839    throttle_completions.store(true, atomic::Ordering::Release);
12840    cx.simulate_keystroke(".");
12841    cx.executor()
12842        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12843    cx.executor().run_until_parked();
12844    cx.condition(|editor, _| editor.context_menu_visible())
12845        .await;
12846    cx.update_editor(|editor, _, _| {
12847        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12848        {
12849            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12850                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12851        } else {
12852            panic!("expected completion menu to be open");
12853        }
12854    });
12855}
12856
12857#[gpui::test]
12858async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12859    init_test(cx, |language_settings| {
12860        language_settings.defaults.completions = Some(CompletionSettings {
12861            words: WordsCompletionMode::Enabled,
12862            lsp: true,
12863            lsp_fetch_timeout_ms: 0,
12864            lsp_insert_mode: LspInsertMode::Insert,
12865        });
12866    });
12867
12868    let mut cx = EditorLspTestContext::new_rust(
12869        lsp::ServerCapabilities {
12870            completion_provider: Some(lsp::CompletionOptions {
12871                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12872                ..lsp::CompletionOptions::default()
12873            }),
12874            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12875            ..lsp::ServerCapabilities::default()
12876        },
12877        cx,
12878    )
12879    .await;
12880
12881    let _completion_requests_handler =
12882        cx.lsp
12883            .server
12884            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12885                Ok(Some(lsp::CompletionResponse::Array(vec![
12886                    lsp::CompletionItem {
12887                        label: "first".into(),
12888                        ..lsp::CompletionItem::default()
12889                    },
12890                    lsp::CompletionItem {
12891                        label: "last".into(),
12892                        ..lsp::CompletionItem::default()
12893                    },
12894                ])))
12895            });
12896
12897    cx.set_state(indoc! {"ˇ
12898        first
12899        last
12900        second
12901    "});
12902    cx.simulate_keystroke(".");
12903    cx.executor().run_until_parked();
12904    cx.condition(|editor, _| editor.context_menu_visible())
12905        .await;
12906    cx.update_editor(|editor, _, _| {
12907        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12908        {
12909            assert_eq!(
12910                completion_menu_entries(&menu),
12911                &["first", "last", "second"],
12912                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12913            );
12914        } else {
12915            panic!("expected completion menu to be open");
12916        }
12917    });
12918}
12919
12920#[gpui::test]
12921async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12922    init_test(cx, |language_settings| {
12923        language_settings.defaults.completions = Some(CompletionSettings {
12924            words: WordsCompletionMode::Disabled,
12925            lsp: true,
12926            lsp_fetch_timeout_ms: 0,
12927            lsp_insert_mode: LspInsertMode::Insert,
12928        });
12929    });
12930
12931    let mut cx = EditorLspTestContext::new_rust(
12932        lsp::ServerCapabilities {
12933            completion_provider: Some(lsp::CompletionOptions {
12934                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12935                ..lsp::CompletionOptions::default()
12936            }),
12937            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12938            ..lsp::ServerCapabilities::default()
12939        },
12940        cx,
12941    )
12942    .await;
12943
12944    let _completion_requests_handler =
12945        cx.lsp
12946            .server
12947            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12948                panic!("LSP completions should not be queried when dealing with word completions")
12949            });
12950
12951    cx.set_state(indoc! {"ˇ
12952        first
12953        last
12954        second
12955    "});
12956    cx.update_editor(|editor, window, cx| {
12957        editor.show_word_completions(&ShowWordCompletions, window, cx);
12958    });
12959    cx.executor().run_until_parked();
12960    cx.condition(|editor, _| editor.context_menu_visible())
12961        .await;
12962    cx.update_editor(|editor, _, _| {
12963        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12964        {
12965            assert_eq!(
12966                completion_menu_entries(&menu),
12967                &["first", "last", "second"],
12968                "`ShowWordCompletions` action should show word completions"
12969            );
12970        } else {
12971            panic!("expected completion menu to be open");
12972        }
12973    });
12974
12975    cx.simulate_keystroke("l");
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                &["last"],
12985                "After showing word completions, further editing should filter them and not query the LSP"
12986            );
12987        } else {
12988            panic!("expected completion menu to be open");
12989        }
12990    });
12991}
12992
12993#[gpui::test]
12994async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12995    init_test(cx, |language_settings| {
12996        language_settings.defaults.completions = Some(CompletionSettings {
12997            words: WordsCompletionMode::Fallback,
12998            lsp: false,
12999            lsp_fetch_timeout_ms: 0,
13000            lsp_insert_mode: LspInsertMode::Insert,
13001        });
13002    });
13003
13004    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13005
13006    cx.set_state(indoc! {"ˇ
13007        0_usize
13008        let
13009        33
13010        4.5f32
13011    "});
13012    cx.update_editor(|editor, window, cx| {
13013        editor.show_completions(&ShowCompletions::default(), window, cx);
13014    });
13015    cx.executor().run_until_parked();
13016    cx.condition(|editor, _| editor.context_menu_visible())
13017        .await;
13018    cx.update_editor(|editor, window, cx| {
13019        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13020        {
13021            assert_eq!(
13022                completion_menu_entries(&menu),
13023                &["let"],
13024                "With no digits in the completion query, no digits should be in the word completions"
13025            );
13026        } else {
13027            panic!("expected completion menu to be open");
13028        }
13029        editor.cancel(&Cancel, window, cx);
13030    });
13031
13032    cx.set_state(indoc! {"13033        0_usize
13034        let
13035        3
13036        33.35f32
13037    "});
13038    cx.update_editor(|editor, window, cx| {
13039        editor.show_completions(&ShowCompletions::default(), window, cx);
13040    });
13041    cx.executor().run_until_parked();
13042    cx.condition(|editor, _| editor.context_menu_visible())
13043        .await;
13044    cx.update_editor(|editor, _, _| {
13045        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13046        {
13047            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13048                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13049        } else {
13050            panic!("expected completion menu to be open");
13051        }
13052    });
13053}
13054
13055fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13056    let position = || lsp::Position {
13057        line: params.text_document_position.position.line,
13058        character: params.text_document_position.position.character,
13059    };
13060    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13061        range: lsp::Range {
13062            start: position(),
13063            end: position(),
13064        },
13065        new_text: text.to_string(),
13066    }))
13067}
13068
13069#[gpui::test]
13070async fn test_multiline_completion(cx: &mut TestAppContext) {
13071    init_test(cx, |_| {});
13072
13073    let fs = FakeFs::new(cx.executor());
13074    fs.insert_tree(
13075        path!("/a"),
13076        json!({
13077            "main.ts": "a",
13078        }),
13079    )
13080    .await;
13081
13082    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13083    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13084    let typescript_language = Arc::new(Language::new(
13085        LanguageConfig {
13086            name: "TypeScript".into(),
13087            matcher: LanguageMatcher {
13088                path_suffixes: vec!["ts".to_string()],
13089                ..LanguageMatcher::default()
13090            },
13091            line_comments: vec!["// ".into()],
13092            ..LanguageConfig::default()
13093        },
13094        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13095    ));
13096    language_registry.add(typescript_language.clone());
13097    let mut fake_servers = language_registry.register_fake_lsp(
13098        "TypeScript",
13099        FakeLspAdapter {
13100            capabilities: lsp::ServerCapabilities {
13101                completion_provider: Some(lsp::CompletionOptions {
13102                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13103                    ..lsp::CompletionOptions::default()
13104                }),
13105                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13106                ..lsp::ServerCapabilities::default()
13107            },
13108            // Emulate vtsls label generation
13109            label_for_completion: Some(Box::new(|item, _| {
13110                let text = if let Some(description) = item
13111                    .label_details
13112                    .as_ref()
13113                    .and_then(|label_details| label_details.description.as_ref())
13114                {
13115                    format!("{} {}", item.label, description)
13116                } else if let Some(detail) = &item.detail {
13117                    format!("{} {}", item.label, detail)
13118                } else {
13119                    item.label.clone()
13120                };
13121                let len = text.len();
13122                Some(language::CodeLabel {
13123                    text,
13124                    runs: Vec::new(),
13125                    filter_range: 0..len,
13126                })
13127            })),
13128            ..FakeLspAdapter::default()
13129        },
13130    );
13131    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13132    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13133    let worktree_id = workspace
13134        .update(cx, |workspace, _window, cx| {
13135            workspace.project().update(cx, |project, cx| {
13136                project.worktrees(cx).next().unwrap().read(cx).id()
13137            })
13138        })
13139        .unwrap();
13140    let _buffer = project
13141        .update(cx, |project, cx| {
13142            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13143        })
13144        .await
13145        .unwrap();
13146    let editor = workspace
13147        .update(cx, |workspace, window, cx| {
13148            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13149        })
13150        .unwrap()
13151        .await
13152        .unwrap()
13153        .downcast::<Editor>()
13154        .unwrap();
13155    let fake_server = fake_servers.next().await.unwrap();
13156
13157    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13158    let multiline_label_2 = "a\nb\nc\n";
13159    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13160    let multiline_description = "d\ne\nf\n";
13161    let multiline_detail_2 = "g\nh\ni\n";
13162
13163    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13164        move |params, _| async move {
13165            Ok(Some(lsp::CompletionResponse::Array(vec![
13166                lsp::CompletionItem {
13167                    label: multiline_label.to_string(),
13168                    text_edit: gen_text_edit(&params, "new_text_1"),
13169                    ..lsp::CompletionItem::default()
13170                },
13171                lsp::CompletionItem {
13172                    label: "single line label 1".to_string(),
13173                    detail: Some(multiline_detail.to_string()),
13174                    text_edit: gen_text_edit(&params, "new_text_2"),
13175                    ..lsp::CompletionItem::default()
13176                },
13177                lsp::CompletionItem {
13178                    label: "single line label 2".to_string(),
13179                    label_details: Some(lsp::CompletionItemLabelDetails {
13180                        description: Some(multiline_description.to_string()),
13181                        detail: None,
13182                    }),
13183                    text_edit: gen_text_edit(&params, "new_text_2"),
13184                    ..lsp::CompletionItem::default()
13185                },
13186                lsp::CompletionItem {
13187                    label: multiline_label_2.to_string(),
13188                    detail: Some(multiline_detail_2.to_string()),
13189                    text_edit: gen_text_edit(&params, "new_text_3"),
13190                    ..lsp::CompletionItem::default()
13191                },
13192                lsp::CompletionItem {
13193                    label: "Label with many     spaces and \t but without newlines".to_string(),
13194                    detail: Some(
13195                        "Details with many     spaces and \t but without newlines".to_string(),
13196                    ),
13197                    text_edit: gen_text_edit(&params, "new_text_4"),
13198                    ..lsp::CompletionItem::default()
13199                },
13200            ])))
13201        },
13202    );
13203
13204    editor.update_in(cx, |editor, window, cx| {
13205        cx.focus_self(window);
13206        editor.move_to_end(&MoveToEnd, window, cx);
13207        editor.handle_input(".", window, cx);
13208    });
13209    cx.run_until_parked();
13210    completion_handle.next().await.unwrap();
13211
13212    editor.update(cx, |editor, _| {
13213        assert!(editor.context_menu_visible());
13214        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13215        {
13216            let completion_labels = menu
13217                .completions
13218                .borrow()
13219                .iter()
13220                .map(|c| c.label.text.clone())
13221                .collect::<Vec<_>>();
13222            assert_eq!(
13223                completion_labels,
13224                &[
13225                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13226                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13227                    "single line label 2 d e f ",
13228                    "a b c g h i ",
13229                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13230                ],
13231                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13232            );
13233
13234            for completion in menu
13235                .completions
13236                .borrow()
13237                .iter() {
13238                    assert_eq!(
13239                        completion.label.filter_range,
13240                        0..completion.label.text.len(),
13241                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13242                    );
13243                }
13244        } else {
13245            panic!("expected completion menu to be open");
13246        }
13247    });
13248}
13249
13250#[gpui::test]
13251async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13252    init_test(cx, |_| {});
13253    let mut cx = EditorLspTestContext::new_rust(
13254        lsp::ServerCapabilities {
13255            completion_provider: Some(lsp::CompletionOptions {
13256                trigger_characters: Some(vec![".".to_string()]),
13257                ..Default::default()
13258            }),
13259            ..Default::default()
13260        },
13261        cx,
13262    )
13263    .await;
13264    cx.lsp
13265        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13266            Ok(Some(lsp::CompletionResponse::Array(vec![
13267                lsp::CompletionItem {
13268                    label: "first".into(),
13269                    ..Default::default()
13270                },
13271                lsp::CompletionItem {
13272                    label: "last".into(),
13273                    ..Default::default()
13274                },
13275            ])))
13276        });
13277    cx.set_state("variableˇ");
13278    cx.simulate_keystroke(".");
13279    cx.executor().run_until_parked();
13280
13281    cx.update_editor(|editor, _, _| {
13282        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13283        {
13284            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13285        } else {
13286            panic!("expected completion menu to be open");
13287        }
13288    });
13289
13290    cx.update_editor(|editor, window, cx| {
13291        editor.move_page_down(&MovePageDown::default(), window, cx);
13292        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13293        {
13294            assert!(
13295                menu.selected_item == 1,
13296                "expected PageDown to select the last item from the context menu"
13297            );
13298        } else {
13299            panic!("expected completion menu to stay open after PageDown");
13300        }
13301    });
13302
13303    cx.update_editor(|editor, window, cx| {
13304        editor.move_page_up(&MovePageUp::default(), window, cx);
13305        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13306        {
13307            assert!(
13308                menu.selected_item == 0,
13309                "expected PageUp to select the first item from the context menu"
13310            );
13311        } else {
13312            panic!("expected completion menu to stay open after PageUp");
13313        }
13314    });
13315}
13316
13317#[gpui::test]
13318async fn test_as_is_completions(cx: &mut TestAppContext) {
13319    init_test(cx, |_| {});
13320    let mut cx = EditorLspTestContext::new_rust(
13321        lsp::ServerCapabilities {
13322            completion_provider: Some(lsp::CompletionOptions {
13323                ..Default::default()
13324            }),
13325            ..Default::default()
13326        },
13327        cx,
13328    )
13329    .await;
13330    cx.lsp
13331        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13332            Ok(Some(lsp::CompletionResponse::Array(vec![
13333                lsp::CompletionItem {
13334                    label: "unsafe".into(),
13335                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13336                        range: lsp::Range {
13337                            start: lsp::Position {
13338                                line: 1,
13339                                character: 2,
13340                            },
13341                            end: lsp::Position {
13342                                line: 1,
13343                                character: 3,
13344                            },
13345                        },
13346                        new_text: "unsafe".to_string(),
13347                    })),
13348                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13349                    ..Default::default()
13350                },
13351            ])))
13352        });
13353    cx.set_state("fn a() {}\n");
13354    cx.executor().run_until_parked();
13355    cx.update_editor(|editor, window, cx| {
13356        editor.show_completions(
13357            &ShowCompletions {
13358                trigger: Some("\n".into()),
13359            },
13360            window,
13361            cx,
13362        );
13363    });
13364    cx.executor().run_until_parked();
13365
13366    cx.update_editor(|editor, window, cx| {
13367        editor.confirm_completion(&Default::default(), window, cx)
13368    });
13369    cx.executor().run_until_parked();
13370    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13371}
13372
13373#[gpui::test]
13374async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13375    init_test(cx, |_| {});
13376
13377    let mut cx = EditorLspTestContext::new_rust(
13378        lsp::ServerCapabilities {
13379            completion_provider: Some(lsp::CompletionOptions {
13380                trigger_characters: Some(vec![".".to_string()]),
13381                resolve_provider: Some(true),
13382                ..Default::default()
13383            }),
13384            ..Default::default()
13385        },
13386        cx,
13387    )
13388    .await;
13389
13390    cx.set_state("fn main() { let a = 2ˇ; }");
13391    cx.simulate_keystroke(".");
13392    let completion_item = lsp::CompletionItem {
13393        label: "Some".into(),
13394        kind: Some(lsp::CompletionItemKind::SNIPPET),
13395        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13396        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13397            kind: lsp::MarkupKind::Markdown,
13398            value: "```rust\nSome(2)\n```".to_string(),
13399        })),
13400        deprecated: Some(false),
13401        sort_text: Some("Some".to_string()),
13402        filter_text: Some("Some".to_string()),
13403        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13404        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13405            range: lsp::Range {
13406                start: lsp::Position {
13407                    line: 0,
13408                    character: 22,
13409                },
13410                end: lsp::Position {
13411                    line: 0,
13412                    character: 22,
13413                },
13414            },
13415            new_text: "Some(2)".to_string(),
13416        })),
13417        additional_text_edits: Some(vec![lsp::TextEdit {
13418            range: lsp::Range {
13419                start: lsp::Position {
13420                    line: 0,
13421                    character: 20,
13422                },
13423                end: lsp::Position {
13424                    line: 0,
13425                    character: 22,
13426                },
13427            },
13428            new_text: "".to_string(),
13429        }]),
13430        ..Default::default()
13431    };
13432
13433    let closure_completion_item = completion_item.clone();
13434    let counter = Arc::new(AtomicUsize::new(0));
13435    let counter_clone = counter.clone();
13436    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13437        let task_completion_item = closure_completion_item.clone();
13438        counter_clone.fetch_add(1, atomic::Ordering::Release);
13439        async move {
13440            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13441                is_incomplete: true,
13442                item_defaults: None,
13443                items: vec![task_completion_item],
13444            })))
13445        }
13446    });
13447
13448    cx.condition(|editor, _| editor.context_menu_visible())
13449        .await;
13450    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13451    assert!(request.next().await.is_some());
13452    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13453
13454    cx.simulate_keystrokes("S o m");
13455    cx.condition(|editor, _| editor.context_menu_visible())
13456        .await;
13457    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13458    assert!(request.next().await.is_some());
13459    assert!(request.next().await.is_some());
13460    assert!(request.next().await.is_some());
13461    request.close();
13462    assert!(request.next().await.is_none());
13463    assert_eq!(
13464        counter.load(atomic::Ordering::Acquire),
13465        4,
13466        "With the completions menu open, only one LSP request should happen per input"
13467    );
13468}
13469
13470#[gpui::test]
13471async fn test_toggle_comment(cx: &mut TestAppContext) {
13472    init_test(cx, |_| {});
13473    let mut cx = EditorTestContext::new(cx).await;
13474    let language = Arc::new(Language::new(
13475        LanguageConfig {
13476            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13477            ..Default::default()
13478        },
13479        Some(tree_sitter_rust::LANGUAGE.into()),
13480    ));
13481    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13482
13483    // If multiple selections intersect a line, the line is only toggled once.
13484    cx.set_state(indoc! {"
13485        fn a() {
13486            «//b();
13487            ˇ»// «c();
13488            //ˇ»  d();
13489        }
13490    "});
13491
13492    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13493
13494    cx.assert_editor_state(indoc! {"
13495        fn a() {
13496            «b();
13497            c();
13498            ˇ» d();
13499        }
13500    "});
13501
13502    // The comment prefix is inserted at the same column for every line in a
13503    // selection.
13504    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13505
13506    cx.assert_editor_state(indoc! {"
13507        fn a() {
13508            // «b();
13509            // c();
13510            ˇ»//  d();
13511        }
13512    "});
13513
13514    // If a selection ends at the beginning of a line, that line is not toggled.
13515    cx.set_selections_state(indoc! {"
13516        fn a() {
13517            // b();
13518            «// c();
13519        ˇ»    //  d();
13520        }
13521    "});
13522
13523    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13524
13525    cx.assert_editor_state(indoc! {"
13526        fn a() {
13527            // b();
13528            «c();
13529        ˇ»    //  d();
13530        }
13531    "});
13532
13533    // If a selection span a single line and is empty, the line is toggled.
13534    cx.set_state(indoc! {"
13535        fn a() {
13536            a();
13537            b();
13538        ˇ
13539        }
13540    "});
13541
13542    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13543
13544    cx.assert_editor_state(indoc! {"
13545        fn a() {
13546            a();
13547            b();
13548        //•ˇ
13549        }
13550    "});
13551
13552    // If a selection span multiple lines, empty lines are not toggled.
13553    cx.set_state(indoc! {"
13554        fn a() {
13555            «a();
13556
13557            c();ˇ»
13558        }
13559    "});
13560
13561    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13562
13563    cx.assert_editor_state(indoc! {"
13564        fn a() {
13565            // «a();
13566
13567            // c();ˇ»
13568        }
13569    "});
13570
13571    // If a selection includes multiple comment prefixes, all lines are uncommented.
13572    cx.set_state(indoc! {"
13573        fn a() {
13574            «// a();
13575            /// b();
13576            //! c();ˇ»
13577        }
13578    "});
13579
13580    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13581
13582    cx.assert_editor_state(indoc! {"
13583        fn a() {
13584            «a();
13585            b();
13586            c();ˇ»
13587        }
13588    "});
13589}
13590
13591#[gpui::test]
13592async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13593    init_test(cx, |_| {});
13594    let mut cx = EditorTestContext::new(cx).await;
13595    let language = Arc::new(Language::new(
13596        LanguageConfig {
13597            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13598            ..Default::default()
13599        },
13600        Some(tree_sitter_rust::LANGUAGE.into()),
13601    ));
13602    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13603
13604    let toggle_comments = &ToggleComments {
13605        advance_downwards: false,
13606        ignore_indent: true,
13607    };
13608
13609    // If multiple selections intersect a line, the line is only toggled once.
13610    cx.set_state(indoc! {"
13611        fn a() {
13612        //    «b();
13613        //    c();
13614        //    ˇ» d();
13615        }
13616    "});
13617
13618    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13619
13620    cx.assert_editor_state(indoc! {"
13621        fn a() {
13622            «b();
13623            c();
13624            ˇ» d();
13625        }
13626    "});
13627
13628    // The comment prefix is inserted at the beginning of each line
13629    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13630
13631    cx.assert_editor_state(indoc! {"
13632        fn a() {
13633        //    «b();
13634        //    c();
13635        //    ˇ» d();
13636        }
13637    "});
13638
13639    // If a selection ends at the beginning of a line, that line is not toggled.
13640    cx.set_selections_state(indoc! {"
13641        fn a() {
13642        //    b();
13643        //    «c();
13644        ˇ»//     d();
13645        }
13646    "});
13647
13648    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13649
13650    cx.assert_editor_state(indoc! {"
13651        fn a() {
13652        //    b();
13653            «c();
13654        ˇ»//     d();
13655        }
13656    "});
13657
13658    // If a selection span a single line and is empty, the line is toggled.
13659    cx.set_state(indoc! {"
13660        fn a() {
13661            a();
13662            b();
13663        ˇ
13664        }
13665    "});
13666
13667    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13668
13669    cx.assert_editor_state(indoc! {"
13670        fn a() {
13671            a();
13672            b();
13673        //ˇ
13674        }
13675    "});
13676
13677    // If a selection span multiple lines, empty lines are not toggled.
13678    cx.set_state(indoc! {"
13679        fn a() {
13680            «a();
13681
13682            c();ˇ»
13683        }
13684    "});
13685
13686    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13687
13688    cx.assert_editor_state(indoc! {"
13689        fn a() {
13690        //    «a();
13691
13692        //    c();ˇ»
13693        }
13694    "});
13695
13696    // If a selection includes multiple comment prefixes, all lines are uncommented.
13697    cx.set_state(indoc! {"
13698        fn a() {
13699        //    «a();
13700        ///    b();
13701        //!    c();ˇ»
13702        }
13703    "});
13704
13705    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13706
13707    cx.assert_editor_state(indoc! {"
13708        fn a() {
13709            «a();
13710            b();
13711            c();ˇ»
13712        }
13713    "});
13714}
13715
13716#[gpui::test]
13717async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13718    init_test(cx, |_| {});
13719
13720    let language = Arc::new(Language::new(
13721        LanguageConfig {
13722            line_comments: vec!["// ".into()],
13723            ..Default::default()
13724        },
13725        Some(tree_sitter_rust::LANGUAGE.into()),
13726    ));
13727
13728    let mut cx = EditorTestContext::new(cx).await;
13729
13730    cx.language_registry().add(language.clone());
13731    cx.update_buffer(|buffer, cx| {
13732        buffer.set_language(Some(language), cx);
13733    });
13734
13735    let toggle_comments = &ToggleComments {
13736        advance_downwards: true,
13737        ignore_indent: false,
13738    };
13739
13740    // Single cursor on one line -> advance
13741    // Cursor moves horizontally 3 characters as well on non-blank line
13742    cx.set_state(indoc!(
13743        "fn a() {
13744             ˇdog();
13745             cat();
13746        }"
13747    ));
13748    cx.update_editor(|editor, window, cx| {
13749        editor.toggle_comments(toggle_comments, window, cx);
13750    });
13751    cx.assert_editor_state(indoc!(
13752        "fn a() {
13753             // dog();
13754             catˇ();
13755        }"
13756    ));
13757
13758    // Single selection on one line -> don't advance
13759    cx.set_state(indoc!(
13760        "fn a() {
13761             «dog()ˇ»;
13762             cat();
13763        }"
13764    ));
13765    cx.update_editor(|editor, window, cx| {
13766        editor.toggle_comments(toggle_comments, window, cx);
13767    });
13768    cx.assert_editor_state(indoc!(
13769        "fn a() {
13770             // «dog()ˇ»;
13771             cat();
13772        }"
13773    ));
13774
13775    // Multiple cursors on one line -> advance
13776    cx.set_state(indoc!(
13777        "fn a() {
13778             ˇdˇog();
13779             cat();
13780        }"
13781    ));
13782    cx.update_editor(|editor, window, cx| {
13783        editor.toggle_comments(toggle_comments, window, cx);
13784    });
13785    cx.assert_editor_state(indoc!(
13786        "fn a() {
13787             // dog();
13788             catˇ(ˇ);
13789        }"
13790    ));
13791
13792    // Multiple cursors on one line, with selection -> don't advance
13793    cx.set_state(indoc!(
13794        "fn a() {
13795             ˇdˇog«()ˇ»;
13796             cat();
13797        }"
13798    ));
13799    cx.update_editor(|editor, window, cx| {
13800        editor.toggle_comments(toggle_comments, window, cx);
13801    });
13802    cx.assert_editor_state(indoc!(
13803        "fn a() {
13804             // ˇdˇog«()ˇ»;
13805             cat();
13806        }"
13807    ));
13808
13809    // Single cursor on one line -> advance
13810    // Cursor moves to column 0 on blank line
13811    cx.set_state(indoc!(
13812        "fn a() {
13813             ˇdog();
13814
13815             cat();
13816        }"
13817    ));
13818    cx.update_editor(|editor, window, cx| {
13819        editor.toggle_comments(toggle_comments, window, cx);
13820    });
13821    cx.assert_editor_state(indoc!(
13822        "fn a() {
13823             // dog();
13824        ˇ
13825             cat();
13826        }"
13827    ));
13828
13829    // Single cursor on one line -> advance
13830    // Cursor starts and ends at column 0
13831    cx.set_state(indoc!(
13832        "fn a() {
13833         ˇ    dog();
13834             cat();
13835        }"
13836    ));
13837    cx.update_editor(|editor, window, cx| {
13838        editor.toggle_comments(toggle_comments, window, cx);
13839    });
13840    cx.assert_editor_state(indoc!(
13841        "fn a() {
13842             // dog();
13843         ˇ    cat();
13844        }"
13845    ));
13846}
13847
13848#[gpui::test]
13849async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13850    init_test(cx, |_| {});
13851
13852    let mut cx = EditorTestContext::new(cx).await;
13853
13854    let html_language = Arc::new(
13855        Language::new(
13856            LanguageConfig {
13857                name: "HTML".into(),
13858                block_comment: Some(BlockCommentConfig {
13859                    start: "<!-- ".into(),
13860                    prefix: "".into(),
13861                    end: " -->".into(),
13862                    tab_size: 0,
13863                }),
13864                ..Default::default()
13865            },
13866            Some(tree_sitter_html::LANGUAGE.into()),
13867        )
13868        .with_injection_query(
13869            r#"
13870            (script_element
13871                (raw_text) @injection.content
13872                (#set! injection.language "javascript"))
13873            "#,
13874        )
13875        .unwrap(),
13876    );
13877
13878    let javascript_language = Arc::new(Language::new(
13879        LanguageConfig {
13880            name: "JavaScript".into(),
13881            line_comments: vec!["// ".into()],
13882            ..Default::default()
13883        },
13884        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13885    ));
13886
13887    cx.language_registry().add(html_language.clone());
13888    cx.language_registry().add(javascript_language.clone());
13889    cx.update_buffer(|buffer, cx| {
13890        buffer.set_language(Some(html_language), cx);
13891    });
13892
13893    // Toggle comments for empty selections
13894    cx.set_state(
13895        &r#"
13896            <p>A</p>ˇ
13897            <p>B</p>ˇ
13898            <p>C</p>ˇ
13899        "#
13900        .unindent(),
13901    );
13902    cx.update_editor(|editor, window, cx| {
13903        editor.toggle_comments(&ToggleComments::default(), window, cx)
13904    });
13905    cx.assert_editor_state(
13906        &r#"
13907            <!-- <p>A</p>ˇ -->
13908            <!-- <p>B</p>ˇ -->
13909            <!-- <p>C</p>ˇ -->
13910        "#
13911        .unindent(),
13912    );
13913    cx.update_editor(|editor, window, cx| {
13914        editor.toggle_comments(&ToggleComments::default(), window, cx)
13915    });
13916    cx.assert_editor_state(
13917        &r#"
13918            <p>A</p>ˇ
13919            <p>B</p>ˇ
13920            <p>C</p>ˇ
13921        "#
13922        .unindent(),
13923    );
13924
13925    // Toggle comments for mixture of empty and non-empty selections, where
13926    // multiple selections occupy a given line.
13927    cx.set_state(
13928        &r#"
13929            <p>A«</p>
13930            <p>ˇ»B</p>ˇ
13931            <p>C«</p>
13932            <p>ˇ»D</p>ˇ
13933        "#
13934        .unindent(),
13935    );
13936
13937    cx.update_editor(|editor, window, cx| {
13938        editor.toggle_comments(&ToggleComments::default(), window, cx)
13939    });
13940    cx.assert_editor_state(
13941        &r#"
13942            <!-- <p>A«</p>
13943            <p>ˇ»B</p>ˇ -->
13944            <!-- <p>C«</p>
13945            <p>ˇ»D</p>ˇ -->
13946        "#
13947        .unindent(),
13948    );
13949    cx.update_editor(|editor, window, cx| {
13950        editor.toggle_comments(&ToggleComments::default(), window, cx)
13951    });
13952    cx.assert_editor_state(
13953        &r#"
13954            <p>A«</p>
13955            <p>ˇ»B</p>ˇ
13956            <p>C«</p>
13957            <p>ˇ»D</p>ˇ
13958        "#
13959        .unindent(),
13960    );
13961
13962    // Toggle comments when different languages are active for different
13963    // selections.
13964    cx.set_state(
13965        &r#"
13966            ˇ<script>
13967                ˇvar x = new Y();
13968            ˇ</script>
13969        "#
13970        .unindent(),
13971    );
13972    cx.executor().run_until_parked();
13973    cx.update_editor(|editor, window, cx| {
13974        editor.toggle_comments(&ToggleComments::default(), window, cx)
13975    });
13976    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13977    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13978    cx.assert_editor_state(
13979        &r#"
13980            <!-- ˇ<script> -->
13981                // ˇvar x = new Y();
13982            <!-- ˇ</script> -->
13983        "#
13984        .unindent(),
13985    );
13986}
13987
13988#[gpui::test]
13989fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13990    init_test(cx, |_| {});
13991
13992    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13993    let multibuffer = cx.new(|cx| {
13994        let mut multibuffer = MultiBuffer::new(ReadWrite);
13995        multibuffer.push_excerpts(
13996            buffer.clone(),
13997            [
13998                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13999                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14000            ],
14001            cx,
14002        );
14003        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14004        multibuffer
14005    });
14006
14007    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14008    editor.update_in(cx, |editor, window, cx| {
14009        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14010        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14011            s.select_ranges([
14012                Point::new(0, 0)..Point::new(0, 0),
14013                Point::new(1, 0)..Point::new(1, 0),
14014            ])
14015        });
14016
14017        editor.handle_input("X", window, cx);
14018        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14019        assert_eq!(
14020            editor.selections.ranges(cx),
14021            [
14022                Point::new(0, 1)..Point::new(0, 1),
14023                Point::new(1, 1)..Point::new(1, 1),
14024            ]
14025        );
14026
14027        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14029            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14030        });
14031        editor.backspace(&Default::default(), window, cx);
14032        assert_eq!(editor.text(cx), "Xa\nbbb");
14033        assert_eq!(
14034            editor.selections.ranges(cx),
14035            [Point::new(1, 0)..Point::new(1, 0)]
14036        );
14037
14038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14039            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14040        });
14041        editor.backspace(&Default::default(), window, cx);
14042        assert_eq!(editor.text(cx), "X\nbb");
14043        assert_eq!(
14044            editor.selections.ranges(cx),
14045            [Point::new(0, 1)..Point::new(0, 1)]
14046        );
14047    });
14048}
14049
14050#[gpui::test]
14051fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14052    init_test(cx, |_| {});
14053
14054    let markers = vec![('[', ']').into(), ('(', ')').into()];
14055    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14056        indoc! {"
14057            [aaaa
14058            (bbbb]
14059            cccc)",
14060        },
14061        markers.clone(),
14062    );
14063    let excerpt_ranges = markers.into_iter().map(|marker| {
14064        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14065        ExcerptRange::new(context.clone())
14066    });
14067    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14068    let multibuffer = cx.new(|cx| {
14069        let mut multibuffer = MultiBuffer::new(ReadWrite);
14070        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14071        multibuffer
14072    });
14073
14074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14075    editor.update_in(cx, |editor, window, cx| {
14076        let (expected_text, selection_ranges) = marked_text_ranges(
14077            indoc! {"
14078                aaaa
14079                bˇbbb
14080                bˇbbˇb
14081                cccc"
14082            },
14083            true,
14084        );
14085        assert_eq!(editor.text(cx), expected_text);
14086        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14087            s.select_ranges(selection_ranges)
14088        });
14089
14090        editor.handle_input("X", window, cx);
14091
14092        let (expected_text, expected_selections) = marked_text_ranges(
14093            indoc! {"
14094                aaaa
14095                bXˇbbXb
14096                bXˇbbXˇb
14097                cccc"
14098            },
14099            false,
14100        );
14101        assert_eq!(editor.text(cx), expected_text);
14102        assert_eq!(editor.selections.ranges(cx), expected_selections);
14103
14104        editor.newline(&Newline, window, cx);
14105        let (expected_text, expected_selections) = marked_text_ranges(
14106            indoc! {"
14107                aaaa
14108                bX
14109                ˇbbX
14110                b
14111                bX
14112                ˇbbX
14113                ˇb
14114                cccc"
14115            },
14116            false,
14117        );
14118        assert_eq!(editor.text(cx), expected_text);
14119        assert_eq!(editor.selections.ranges(cx), expected_selections);
14120    });
14121}
14122
14123#[gpui::test]
14124fn test_refresh_selections(cx: &mut TestAppContext) {
14125    init_test(cx, |_| {});
14126
14127    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14128    let mut excerpt1_id = None;
14129    let multibuffer = cx.new(|cx| {
14130        let mut multibuffer = MultiBuffer::new(ReadWrite);
14131        excerpt1_id = multibuffer
14132            .push_excerpts(
14133                buffer.clone(),
14134                [
14135                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14136                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14137                ],
14138                cx,
14139            )
14140            .into_iter()
14141            .next();
14142        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14143        multibuffer
14144    });
14145
14146    let editor = cx.add_window(|window, cx| {
14147        let mut editor = build_editor(multibuffer.clone(), window, cx);
14148        let snapshot = editor.snapshot(window, cx);
14149        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14150            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14151        });
14152        editor.begin_selection(
14153            Point::new(2, 1).to_display_point(&snapshot),
14154            true,
14155            1,
14156            window,
14157            cx,
14158        );
14159        assert_eq!(
14160            editor.selections.ranges(cx),
14161            [
14162                Point::new(1, 3)..Point::new(1, 3),
14163                Point::new(2, 1)..Point::new(2, 1),
14164            ]
14165        );
14166        editor
14167    });
14168
14169    // Refreshing selections is a no-op when excerpts haven't changed.
14170    _ = editor.update(cx, |editor, window, cx| {
14171        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14172        assert_eq!(
14173            editor.selections.ranges(cx),
14174            [
14175                Point::new(1, 3)..Point::new(1, 3),
14176                Point::new(2, 1)..Point::new(2, 1),
14177            ]
14178        );
14179    });
14180
14181    multibuffer.update(cx, |multibuffer, cx| {
14182        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14183    });
14184    _ = editor.update(cx, |editor, window, cx| {
14185        // Removing an excerpt causes the first selection to become degenerate.
14186        assert_eq!(
14187            editor.selections.ranges(cx),
14188            [
14189                Point::new(0, 0)..Point::new(0, 0),
14190                Point::new(0, 1)..Point::new(0, 1)
14191            ]
14192        );
14193
14194        // Refreshing selections will relocate the first selection to the original buffer
14195        // location.
14196        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14197        assert_eq!(
14198            editor.selections.ranges(cx),
14199            [
14200                Point::new(0, 1)..Point::new(0, 1),
14201                Point::new(0, 3)..Point::new(0, 3)
14202            ]
14203        );
14204        assert!(editor.selections.pending_anchor().is_some());
14205    });
14206}
14207
14208#[gpui::test]
14209fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14210    init_test(cx, |_| {});
14211
14212    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14213    let mut excerpt1_id = None;
14214    let multibuffer = cx.new(|cx| {
14215        let mut multibuffer = MultiBuffer::new(ReadWrite);
14216        excerpt1_id = multibuffer
14217            .push_excerpts(
14218                buffer.clone(),
14219                [
14220                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14221                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14222                ],
14223                cx,
14224            )
14225            .into_iter()
14226            .next();
14227        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14228        multibuffer
14229    });
14230
14231    let editor = cx.add_window(|window, cx| {
14232        let mut editor = build_editor(multibuffer.clone(), window, cx);
14233        let snapshot = editor.snapshot(window, cx);
14234        editor.begin_selection(
14235            Point::new(1, 3).to_display_point(&snapshot),
14236            false,
14237            1,
14238            window,
14239            cx,
14240        );
14241        assert_eq!(
14242            editor.selections.ranges(cx),
14243            [Point::new(1, 3)..Point::new(1, 3)]
14244        );
14245        editor
14246    });
14247
14248    multibuffer.update(cx, |multibuffer, cx| {
14249        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14250    });
14251    _ = editor.update(cx, |editor, window, cx| {
14252        assert_eq!(
14253            editor.selections.ranges(cx),
14254            [Point::new(0, 0)..Point::new(0, 0)]
14255        );
14256
14257        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14258        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14259        assert_eq!(
14260            editor.selections.ranges(cx),
14261            [Point::new(0, 3)..Point::new(0, 3)]
14262        );
14263        assert!(editor.selections.pending_anchor().is_some());
14264    });
14265}
14266
14267#[gpui::test]
14268async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14269    init_test(cx, |_| {});
14270
14271    let language = Arc::new(
14272        Language::new(
14273            LanguageConfig {
14274                brackets: BracketPairConfig {
14275                    pairs: vec![
14276                        BracketPair {
14277                            start: "{".to_string(),
14278                            end: "}".to_string(),
14279                            close: true,
14280                            surround: true,
14281                            newline: true,
14282                        },
14283                        BracketPair {
14284                            start: "/* ".to_string(),
14285                            end: " */".to_string(),
14286                            close: true,
14287                            surround: true,
14288                            newline: true,
14289                        },
14290                    ],
14291                    ..Default::default()
14292                },
14293                ..Default::default()
14294            },
14295            Some(tree_sitter_rust::LANGUAGE.into()),
14296        )
14297        .with_indents_query("")
14298        .unwrap(),
14299    );
14300
14301    let text = concat!(
14302        "{   }\n",     //
14303        "  x\n",       //
14304        "  /*   */\n", //
14305        "x\n",         //
14306        "{{} }\n",     //
14307    );
14308
14309    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14310    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14311    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14312    editor
14313        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14314        .await;
14315
14316    editor.update_in(cx, |editor, window, cx| {
14317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14318            s.select_display_ranges([
14319                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14320                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14321                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14322            ])
14323        });
14324        editor.newline(&Newline, window, cx);
14325
14326        assert_eq!(
14327            editor.buffer().read(cx).read(cx).text(),
14328            concat!(
14329                "{ \n",    // Suppress rustfmt
14330                "\n",      //
14331                "}\n",     //
14332                "  x\n",   //
14333                "  /* \n", //
14334                "  \n",    //
14335                "  */\n",  //
14336                "x\n",     //
14337                "{{} \n",  //
14338                "}\n",     //
14339            )
14340        );
14341    });
14342}
14343
14344#[gpui::test]
14345fn test_highlighted_ranges(cx: &mut TestAppContext) {
14346    init_test(cx, |_| {});
14347
14348    let editor = cx.add_window(|window, cx| {
14349        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14350        build_editor(buffer.clone(), window, cx)
14351    });
14352
14353    _ = editor.update(cx, |editor, window, cx| {
14354        struct Type1;
14355        struct Type2;
14356
14357        let buffer = editor.buffer.read(cx).snapshot(cx);
14358
14359        let anchor_range =
14360            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14361
14362        editor.highlight_background::<Type1>(
14363            &[
14364                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14365                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14366                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14367                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14368            ],
14369            |_| Hsla::red(),
14370            cx,
14371        );
14372        editor.highlight_background::<Type2>(
14373            &[
14374                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14375                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14376                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14377                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14378            ],
14379            |_| Hsla::green(),
14380            cx,
14381        );
14382
14383        let snapshot = editor.snapshot(window, cx);
14384        let mut highlighted_ranges = editor.background_highlights_in_range(
14385            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14386            &snapshot,
14387            cx.theme(),
14388        );
14389        // Enforce a consistent ordering based on color without relying on the ordering of the
14390        // highlight's `TypeId` which is non-executor.
14391        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14392        assert_eq!(
14393            highlighted_ranges,
14394            &[
14395                (
14396                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14397                    Hsla::red(),
14398                ),
14399                (
14400                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14401                    Hsla::red(),
14402                ),
14403                (
14404                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14405                    Hsla::green(),
14406                ),
14407                (
14408                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14409                    Hsla::green(),
14410                ),
14411            ]
14412        );
14413        assert_eq!(
14414            editor.background_highlights_in_range(
14415                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14416                &snapshot,
14417                cx.theme(),
14418            ),
14419            &[(
14420                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14421                Hsla::red(),
14422            )]
14423        );
14424    });
14425}
14426
14427#[gpui::test]
14428async fn test_following(cx: &mut TestAppContext) {
14429    init_test(cx, |_| {});
14430
14431    let fs = FakeFs::new(cx.executor());
14432    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14433
14434    let buffer = project.update(cx, |project, cx| {
14435        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14436        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14437    });
14438    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14439    let follower = cx.update(|cx| {
14440        cx.open_window(
14441            WindowOptions {
14442                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14443                    gpui::Point::new(px(0.), px(0.)),
14444                    gpui::Point::new(px(10.), px(80.)),
14445                ))),
14446                ..Default::default()
14447            },
14448            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14449        )
14450        .unwrap()
14451    });
14452
14453    let is_still_following = Rc::new(RefCell::new(true));
14454    let follower_edit_event_count = Rc::new(RefCell::new(0));
14455    let pending_update = Rc::new(RefCell::new(None));
14456    let leader_entity = leader.root(cx).unwrap();
14457    let follower_entity = follower.root(cx).unwrap();
14458    _ = follower.update(cx, {
14459        let update = pending_update.clone();
14460        let is_still_following = is_still_following.clone();
14461        let follower_edit_event_count = follower_edit_event_count.clone();
14462        |_, window, cx| {
14463            cx.subscribe_in(
14464                &leader_entity,
14465                window,
14466                move |_, leader, event, window, cx| {
14467                    leader.read(cx).add_event_to_update_proto(
14468                        event,
14469                        &mut update.borrow_mut(),
14470                        window,
14471                        cx,
14472                    );
14473                },
14474            )
14475            .detach();
14476
14477            cx.subscribe_in(
14478                &follower_entity,
14479                window,
14480                move |_, _, event: &EditorEvent, _window, _cx| {
14481                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14482                        *is_still_following.borrow_mut() = false;
14483                    }
14484
14485                    if let EditorEvent::BufferEdited = event {
14486                        *follower_edit_event_count.borrow_mut() += 1;
14487                    }
14488                },
14489            )
14490            .detach();
14491        }
14492    });
14493
14494    // Update the selections only
14495    _ = leader.update(cx, |leader, window, cx| {
14496        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14497            s.select_ranges([1..1])
14498        });
14499    });
14500    follower
14501        .update(cx, |follower, window, cx| {
14502            follower.apply_update_proto(
14503                &project,
14504                pending_update.borrow_mut().take().unwrap(),
14505                window,
14506                cx,
14507            )
14508        })
14509        .unwrap()
14510        .await
14511        .unwrap();
14512    _ = follower.update(cx, |follower, _, cx| {
14513        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14514    });
14515    assert!(*is_still_following.borrow());
14516    assert_eq!(*follower_edit_event_count.borrow(), 0);
14517
14518    // Update the scroll position only
14519    _ = leader.update(cx, |leader, window, cx| {
14520        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14521    });
14522    follower
14523        .update(cx, |follower, window, cx| {
14524            follower.apply_update_proto(
14525                &project,
14526                pending_update.borrow_mut().take().unwrap(),
14527                window,
14528                cx,
14529            )
14530        })
14531        .unwrap()
14532        .await
14533        .unwrap();
14534    assert_eq!(
14535        follower
14536            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14537            .unwrap(),
14538        gpui::Point::new(1.5, 3.5)
14539    );
14540    assert!(*is_still_following.borrow());
14541    assert_eq!(*follower_edit_event_count.borrow(), 0);
14542
14543    // Update the selections and scroll position. The follower's scroll position is updated
14544    // via autoscroll, not via the leader's exact scroll position.
14545    _ = leader.update(cx, |leader, window, cx| {
14546        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14547            s.select_ranges([0..0])
14548        });
14549        leader.request_autoscroll(Autoscroll::newest(), cx);
14550        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14551    });
14552    follower
14553        .update(cx, |follower, window, cx| {
14554            follower.apply_update_proto(
14555                &project,
14556                pending_update.borrow_mut().take().unwrap(),
14557                window,
14558                cx,
14559            )
14560        })
14561        .unwrap()
14562        .await
14563        .unwrap();
14564    _ = follower.update(cx, |follower, _, cx| {
14565        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14566        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14567    });
14568    assert!(*is_still_following.borrow());
14569
14570    // Creating a pending selection that precedes another selection
14571    _ = leader.update(cx, |leader, window, cx| {
14572        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14573            s.select_ranges([1..1])
14574        });
14575        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14576    });
14577    follower
14578        .update(cx, |follower, window, cx| {
14579            follower.apply_update_proto(
14580                &project,
14581                pending_update.borrow_mut().take().unwrap(),
14582                window,
14583                cx,
14584            )
14585        })
14586        .unwrap()
14587        .await
14588        .unwrap();
14589    _ = follower.update(cx, |follower, _, cx| {
14590        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14591    });
14592    assert!(*is_still_following.borrow());
14593
14594    // Extend the pending selection so that it surrounds another selection
14595    _ = leader.update(cx, |leader, window, cx| {
14596        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14597    });
14598    follower
14599        .update(cx, |follower, window, cx| {
14600            follower.apply_update_proto(
14601                &project,
14602                pending_update.borrow_mut().take().unwrap(),
14603                window,
14604                cx,
14605            )
14606        })
14607        .unwrap()
14608        .await
14609        .unwrap();
14610    _ = follower.update(cx, |follower, _, cx| {
14611        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14612    });
14613
14614    // Scrolling locally breaks the follow
14615    _ = follower.update(cx, |follower, window, cx| {
14616        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14617        follower.set_scroll_anchor(
14618            ScrollAnchor {
14619                anchor: top_anchor,
14620                offset: gpui::Point::new(0.0, 0.5),
14621            },
14622            window,
14623            cx,
14624        );
14625    });
14626    assert!(!(*is_still_following.borrow()));
14627}
14628
14629#[gpui::test]
14630async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14631    init_test(cx, |_| {});
14632
14633    let fs = FakeFs::new(cx.executor());
14634    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14635    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14636    let pane = workspace
14637        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14638        .unwrap();
14639
14640    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14641
14642    let leader = pane.update_in(cx, |_, window, cx| {
14643        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14644        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14645    });
14646
14647    // Start following the editor when it has no excerpts.
14648    let mut state_message =
14649        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14650    let workspace_entity = workspace.root(cx).unwrap();
14651    let follower_1 = cx
14652        .update_window(*workspace.deref(), |_, window, cx| {
14653            Editor::from_state_proto(
14654                workspace_entity,
14655                ViewId {
14656                    creator: CollaboratorId::PeerId(PeerId::default()),
14657                    id: 0,
14658                },
14659                &mut state_message,
14660                window,
14661                cx,
14662            )
14663        })
14664        .unwrap()
14665        .unwrap()
14666        .await
14667        .unwrap();
14668
14669    let update_message = Rc::new(RefCell::new(None));
14670    follower_1.update_in(cx, {
14671        let update = update_message.clone();
14672        |_, window, cx| {
14673            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14674                leader.read(cx).add_event_to_update_proto(
14675                    event,
14676                    &mut update.borrow_mut(),
14677                    window,
14678                    cx,
14679                );
14680            })
14681            .detach();
14682        }
14683    });
14684
14685    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14686        (
14687            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14688            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14689        )
14690    });
14691
14692    // Insert some excerpts.
14693    leader.update(cx, |leader, cx| {
14694        leader.buffer.update(cx, |multibuffer, cx| {
14695            multibuffer.set_excerpts_for_path(
14696                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14697                buffer_1.clone(),
14698                vec![
14699                    Point::row_range(0..3),
14700                    Point::row_range(1..6),
14701                    Point::row_range(12..15),
14702                ],
14703                0,
14704                cx,
14705            );
14706            multibuffer.set_excerpts_for_path(
14707                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14708                buffer_2.clone(),
14709                vec![Point::row_range(0..6), Point::row_range(8..12)],
14710                0,
14711                cx,
14712            );
14713        });
14714    });
14715
14716    // Apply the update of adding the excerpts.
14717    follower_1
14718        .update_in(cx, |follower, window, cx| {
14719            follower.apply_update_proto(
14720                &project,
14721                update_message.borrow().clone().unwrap(),
14722                window,
14723                cx,
14724            )
14725        })
14726        .await
14727        .unwrap();
14728    assert_eq!(
14729        follower_1.update(cx, |editor, cx| editor.text(cx)),
14730        leader.update(cx, |editor, cx| editor.text(cx))
14731    );
14732    update_message.borrow_mut().take();
14733
14734    // Start following separately after it already has excerpts.
14735    let mut state_message =
14736        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14737    let workspace_entity = workspace.root(cx).unwrap();
14738    let follower_2 = cx
14739        .update_window(*workspace.deref(), |_, window, cx| {
14740            Editor::from_state_proto(
14741                workspace_entity,
14742                ViewId {
14743                    creator: CollaboratorId::PeerId(PeerId::default()),
14744                    id: 0,
14745                },
14746                &mut state_message,
14747                window,
14748                cx,
14749            )
14750        })
14751        .unwrap()
14752        .unwrap()
14753        .await
14754        .unwrap();
14755    assert_eq!(
14756        follower_2.update(cx, |editor, cx| editor.text(cx)),
14757        leader.update(cx, |editor, cx| editor.text(cx))
14758    );
14759
14760    // Remove some excerpts.
14761    leader.update(cx, |leader, cx| {
14762        leader.buffer.update(cx, |multibuffer, cx| {
14763            let excerpt_ids = multibuffer.excerpt_ids();
14764            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14765            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14766        });
14767    });
14768
14769    // Apply the update of removing the excerpts.
14770    follower_1
14771        .update_in(cx, |follower, window, cx| {
14772            follower.apply_update_proto(
14773                &project,
14774                update_message.borrow().clone().unwrap(),
14775                window,
14776                cx,
14777            )
14778        })
14779        .await
14780        .unwrap();
14781    follower_2
14782        .update_in(cx, |follower, window, cx| {
14783            follower.apply_update_proto(
14784                &project,
14785                update_message.borrow().clone().unwrap(),
14786                window,
14787                cx,
14788            )
14789        })
14790        .await
14791        .unwrap();
14792    update_message.borrow_mut().take();
14793    assert_eq!(
14794        follower_1.update(cx, |editor, cx| editor.text(cx)),
14795        leader.update(cx, |editor, cx| editor.text(cx))
14796    );
14797}
14798
14799#[gpui::test]
14800async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14801    init_test(cx, |_| {});
14802
14803    let mut cx = EditorTestContext::new(cx).await;
14804    let lsp_store =
14805        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14806
14807    cx.set_state(indoc! {"
14808        ˇfn func(abc def: i32) -> u32 {
14809        }
14810    "});
14811
14812    cx.update(|_, cx| {
14813        lsp_store.update(cx, |lsp_store, cx| {
14814            lsp_store
14815                .update_diagnostics(
14816                    LanguageServerId(0),
14817                    lsp::PublishDiagnosticsParams {
14818                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14819                        version: None,
14820                        diagnostics: vec![
14821                            lsp::Diagnostic {
14822                                range: lsp::Range::new(
14823                                    lsp::Position::new(0, 11),
14824                                    lsp::Position::new(0, 12),
14825                                ),
14826                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14827                                ..Default::default()
14828                            },
14829                            lsp::Diagnostic {
14830                                range: lsp::Range::new(
14831                                    lsp::Position::new(0, 12),
14832                                    lsp::Position::new(0, 15),
14833                                ),
14834                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14835                                ..Default::default()
14836                            },
14837                            lsp::Diagnostic {
14838                                range: lsp::Range::new(
14839                                    lsp::Position::new(0, 25),
14840                                    lsp::Position::new(0, 28),
14841                                ),
14842                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14843                                ..Default::default()
14844                            },
14845                        ],
14846                    },
14847                    None,
14848                    DiagnosticSourceKind::Pushed,
14849                    &[],
14850                    cx,
14851                )
14852                .unwrap()
14853        });
14854    });
14855
14856    executor.run_until_parked();
14857
14858    cx.update_editor(|editor, window, cx| {
14859        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14860    });
14861
14862    cx.assert_editor_state(indoc! {"
14863        fn func(abc def: i32) -> ˇu32 {
14864        }
14865    "});
14866
14867    cx.update_editor(|editor, window, cx| {
14868        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14869    });
14870
14871    cx.assert_editor_state(indoc! {"
14872        fn func(abc ˇdef: i32) -> u32 {
14873        }
14874    "});
14875
14876    cx.update_editor(|editor, window, cx| {
14877        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14878    });
14879
14880    cx.assert_editor_state(indoc! {"
14881        fn func(abcˇ def: i32) -> u32 {
14882        }
14883    "});
14884
14885    cx.update_editor(|editor, window, cx| {
14886        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14887    });
14888
14889    cx.assert_editor_state(indoc! {"
14890        fn func(abc def: i32) -> ˇu32 {
14891        }
14892    "});
14893}
14894
14895#[gpui::test]
14896async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14897    init_test(cx, |_| {});
14898
14899    let mut cx = EditorTestContext::new(cx).await;
14900
14901    let diff_base = r#"
14902        use some::mod;
14903
14904        const A: u32 = 42;
14905
14906        fn main() {
14907            println!("hello");
14908
14909            println!("world");
14910        }
14911        "#
14912    .unindent();
14913
14914    // Edits are modified, removed, modified, added
14915    cx.set_state(
14916        &r#"
14917        use some::modified;
14918
14919        ˇ
14920        fn main() {
14921            println!("hello there");
14922
14923            println!("around the");
14924            println!("world");
14925        }
14926        "#
14927        .unindent(),
14928    );
14929
14930    cx.set_head_text(&diff_base);
14931    executor.run_until_parked();
14932
14933    cx.update_editor(|editor, window, cx| {
14934        //Wrap around the bottom of the buffer
14935        for _ in 0..3 {
14936            editor.go_to_next_hunk(&GoToHunk, window, cx);
14937        }
14938    });
14939
14940    cx.assert_editor_state(
14941        &r#"
14942        ˇuse some::modified;
14943
14944
14945        fn main() {
14946            println!("hello there");
14947
14948            println!("around the");
14949            println!("world");
14950        }
14951        "#
14952        .unindent(),
14953    );
14954
14955    cx.update_editor(|editor, window, cx| {
14956        //Wrap around the top of the buffer
14957        for _ in 0..2 {
14958            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14959        }
14960    });
14961
14962    cx.assert_editor_state(
14963        &r#"
14964        use some::modified;
14965
14966
14967        fn main() {
14968        ˇ    println!("hello there");
14969
14970            println!("around the");
14971            println!("world");
14972        }
14973        "#
14974        .unindent(),
14975    );
14976
14977    cx.update_editor(|editor, window, cx| {
14978        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14979    });
14980
14981    cx.assert_editor_state(
14982        &r#"
14983        use some::modified;
14984
14985        ˇ
14986        fn main() {
14987            println!("hello there");
14988
14989            println!("around the");
14990            println!("world");
14991        }
14992        "#
14993        .unindent(),
14994    );
14995
14996    cx.update_editor(|editor, window, cx| {
14997        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14998    });
14999
15000    cx.assert_editor_state(
15001        &r#"
15002        ˇuse some::modified;
15003
15004
15005        fn main() {
15006            println!("hello there");
15007
15008            println!("around the");
15009            println!("world");
15010        }
15011        "#
15012        .unindent(),
15013    );
15014
15015    cx.update_editor(|editor, window, cx| {
15016        for _ in 0..2 {
15017            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15018        }
15019    });
15020
15021    cx.assert_editor_state(
15022        &r#"
15023        use some::modified;
15024
15025
15026        fn main() {
15027        ˇ    println!("hello there");
15028
15029            println!("around the");
15030            println!("world");
15031        }
15032        "#
15033        .unindent(),
15034    );
15035
15036    cx.update_editor(|editor, window, cx| {
15037        editor.fold(&Fold, window, cx);
15038    });
15039
15040    cx.update_editor(|editor, window, cx| {
15041        editor.go_to_next_hunk(&GoToHunk, window, cx);
15042    });
15043
15044    cx.assert_editor_state(
15045        &r#"
15046        ˇuse some::modified;
15047
15048
15049        fn main() {
15050            println!("hello there");
15051
15052            println!("around the");
15053            println!("world");
15054        }
15055        "#
15056        .unindent(),
15057    );
15058}
15059
15060#[test]
15061fn test_split_words() {
15062    fn split(text: &str) -> Vec<&str> {
15063        split_words(text).collect()
15064    }
15065
15066    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15067    assert_eq!(split("hello_world"), &["hello_", "world"]);
15068    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15069    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15070    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15071    assert_eq!(split("helloworld"), &["helloworld"]);
15072
15073    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15074}
15075
15076#[gpui::test]
15077async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15078    init_test(cx, |_| {});
15079
15080    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15081    let mut assert = |before, after| {
15082        let _state_context = cx.set_state(before);
15083        cx.run_until_parked();
15084        cx.update_editor(|editor, window, cx| {
15085            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15086        });
15087        cx.run_until_parked();
15088        cx.assert_editor_state(after);
15089    };
15090
15091    // Outside bracket jumps to outside of matching bracket
15092    assert("console.logˇ(var);", "console.log(var)ˇ;");
15093    assert("console.log(var)ˇ;", "console.logˇ(var);");
15094
15095    // Inside bracket jumps to inside of matching bracket
15096    assert("console.log(ˇvar);", "console.log(varˇ);");
15097    assert("console.log(varˇ);", "console.log(ˇvar);");
15098
15099    // When outside a bracket and inside, favor jumping to the inside bracket
15100    assert(
15101        "console.log('foo', [1, 2, 3]ˇ);",
15102        "console.log(ˇ'foo', [1, 2, 3]);",
15103    );
15104    assert(
15105        "console.log(ˇ'foo', [1, 2, 3]);",
15106        "console.log('foo', [1, 2, 3]ˇ);",
15107    );
15108
15109    // Bias forward if two options are equally likely
15110    assert(
15111        "let result = curried_fun()ˇ();",
15112        "let result = curried_fun()()ˇ;",
15113    );
15114
15115    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15116    assert(
15117        indoc! {"
15118            function test() {
15119                console.log('test')ˇ
15120            }"},
15121        indoc! {"
15122            function test() {
15123                console.logˇ('test')
15124            }"},
15125    );
15126}
15127
15128#[gpui::test]
15129async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15130    init_test(cx, |_| {});
15131
15132    let fs = FakeFs::new(cx.executor());
15133    fs.insert_tree(
15134        path!("/a"),
15135        json!({
15136            "main.rs": "fn main() { let a = 5; }",
15137            "other.rs": "// Test file",
15138        }),
15139    )
15140    .await;
15141    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15142
15143    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15144    language_registry.add(Arc::new(Language::new(
15145        LanguageConfig {
15146            name: "Rust".into(),
15147            matcher: LanguageMatcher {
15148                path_suffixes: vec!["rs".to_string()],
15149                ..Default::default()
15150            },
15151            brackets: BracketPairConfig {
15152                pairs: vec![BracketPair {
15153                    start: "{".to_string(),
15154                    end: "}".to_string(),
15155                    close: true,
15156                    surround: true,
15157                    newline: true,
15158                }],
15159                disabled_scopes_by_bracket_ix: Vec::new(),
15160            },
15161            ..Default::default()
15162        },
15163        Some(tree_sitter_rust::LANGUAGE.into()),
15164    )));
15165    let mut fake_servers = language_registry.register_fake_lsp(
15166        "Rust",
15167        FakeLspAdapter {
15168            capabilities: lsp::ServerCapabilities {
15169                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15170                    first_trigger_character: "{".to_string(),
15171                    more_trigger_character: None,
15172                }),
15173                ..Default::default()
15174            },
15175            ..Default::default()
15176        },
15177    );
15178
15179    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15180
15181    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15182
15183    let worktree_id = workspace
15184        .update(cx, |workspace, _, cx| {
15185            workspace.project().update(cx, |project, cx| {
15186                project.worktrees(cx).next().unwrap().read(cx).id()
15187            })
15188        })
15189        .unwrap();
15190
15191    let buffer = project
15192        .update(cx, |project, cx| {
15193            project.open_local_buffer(path!("/a/main.rs"), cx)
15194        })
15195        .await
15196        .unwrap();
15197    let editor_handle = workspace
15198        .update(cx, |workspace, window, cx| {
15199            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15200        })
15201        .unwrap()
15202        .await
15203        .unwrap()
15204        .downcast::<Editor>()
15205        .unwrap();
15206
15207    cx.executor().start_waiting();
15208    let fake_server = fake_servers.next().await.unwrap();
15209
15210    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15211        |params, _| async move {
15212            assert_eq!(
15213                params.text_document_position.text_document.uri,
15214                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15215            );
15216            assert_eq!(
15217                params.text_document_position.position,
15218                lsp::Position::new(0, 21),
15219            );
15220
15221            Ok(Some(vec![lsp::TextEdit {
15222                new_text: "]".to_string(),
15223                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15224            }]))
15225        },
15226    );
15227
15228    editor_handle.update_in(cx, |editor, window, cx| {
15229        window.focus(&editor.focus_handle(cx));
15230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15231            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15232        });
15233        editor.handle_input("{", window, cx);
15234    });
15235
15236    cx.executor().run_until_parked();
15237
15238    buffer.update(cx, |buffer, _| {
15239        assert_eq!(
15240            buffer.text(),
15241            "fn main() { let a = {5}; }",
15242            "No extra braces from on type formatting should appear in the buffer"
15243        )
15244    });
15245}
15246
15247#[gpui::test(iterations = 20, seeds(31))]
15248async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15249    init_test(cx, |_| {});
15250
15251    let mut cx = EditorLspTestContext::new_rust(
15252        lsp::ServerCapabilities {
15253            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15254                first_trigger_character: ".".to_string(),
15255                more_trigger_character: None,
15256            }),
15257            ..Default::default()
15258        },
15259        cx,
15260    )
15261    .await;
15262
15263    cx.update_buffer(|buffer, _| {
15264        // This causes autoindent to be async.
15265        buffer.set_sync_parse_timeout(Duration::ZERO)
15266    });
15267
15268    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15269    cx.simulate_keystroke("\n");
15270    cx.run_until_parked();
15271
15272    let buffer_cloned =
15273        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15274    let mut request =
15275        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15276            let buffer_cloned = buffer_cloned.clone();
15277            async move {
15278                buffer_cloned.update(&mut cx, |buffer, _| {
15279                    assert_eq!(
15280                        buffer.text(),
15281                        "fn c() {\n    d()\n        .\n}\n",
15282                        "OnTypeFormatting should triggered after autoindent applied"
15283                    )
15284                })?;
15285
15286                Ok(Some(vec![]))
15287            }
15288        });
15289
15290    cx.simulate_keystroke(".");
15291    cx.run_until_parked();
15292
15293    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15294    assert!(request.next().await.is_some());
15295    request.close();
15296    assert!(request.next().await.is_none());
15297}
15298
15299#[gpui::test]
15300async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15301    init_test(cx, |_| {});
15302
15303    let fs = FakeFs::new(cx.executor());
15304    fs.insert_tree(
15305        path!("/a"),
15306        json!({
15307            "main.rs": "fn main() { let a = 5; }",
15308            "other.rs": "// Test file",
15309        }),
15310    )
15311    .await;
15312
15313    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15314
15315    let server_restarts = Arc::new(AtomicUsize::new(0));
15316    let closure_restarts = Arc::clone(&server_restarts);
15317    let language_server_name = "test language server";
15318    let language_name: LanguageName = "Rust".into();
15319
15320    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15321    language_registry.add(Arc::new(Language::new(
15322        LanguageConfig {
15323            name: language_name.clone(),
15324            matcher: LanguageMatcher {
15325                path_suffixes: vec!["rs".to_string()],
15326                ..Default::default()
15327            },
15328            ..Default::default()
15329        },
15330        Some(tree_sitter_rust::LANGUAGE.into()),
15331    )));
15332    let mut fake_servers = language_registry.register_fake_lsp(
15333        "Rust",
15334        FakeLspAdapter {
15335            name: language_server_name,
15336            initialization_options: Some(json!({
15337                "testOptionValue": true
15338            })),
15339            initializer: Some(Box::new(move |fake_server| {
15340                let task_restarts = Arc::clone(&closure_restarts);
15341                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15342                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15343                    futures::future::ready(Ok(()))
15344                });
15345            })),
15346            ..Default::default()
15347        },
15348    );
15349
15350    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15351    let _buffer = project
15352        .update(cx, |project, cx| {
15353            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15354        })
15355        .await
15356        .unwrap();
15357    let _fake_server = fake_servers.next().await.unwrap();
15358    update_test_language_settings(cx, |language_settings| {
15359        language_settings.languages.0.insert(
15360            language_name.clone(),
15361            LanguageSettingsContent {
15362                tab_size: NonZeroU32::new(8),
15363                ..Default::default()
15364            },
15365        );
15366    });
15367    cx.executor().run_until_parked();
15368    assert_eq!(
15369        server_restarts.load(atomic::Ordering::Acquire),
15370        0,
15371        "Should not restart LSP server on an unrelated change"
15372    );
15373
15374    update_test_project_settings(cx, |project_settings| {
15375        project_settings.lsp.insert(
15376            "Some other server name".into(),
15377            LspSettings {
15378                binary: None,
15379                settings: None,
15380                initialization_options: Some(json!({
15381                    "some other init value": false
15382                })),
15383                enable_lsp_tasks: false,
15384            },
15385        );
15386    });
15387    cx.executor().run_until_parked();
15388    assert_eq!(
15389        server_restarts.load(atomic::Ordering::Acquire),
15390        0,
15391        "Should not restart LSP server on an unrelated LSP settings change"
15392    );
15393
15394    update_test_project_settings(cx, |project_settings| {
15395        project_settings.lsp.insert(
15396            language_server_name.into(),
15397            LspSettings {
15398                binary: None,
15399                settings: None,
15400                initialization_options: Some(json!({
15401                    "anotherInitValue": false
15402                })),
15403                enable_lsp_tasks: false,
15404            },
15405        );
15406    });
15407    cx.executor().run_until_parked();
15408    assert_eq!(
15409        server_restarts.load(atomic::Ordering::Acquire),
15410        1,
15411        "Should restart LSP server on a related LSP settings change"
15412    );
15413
15414    update_test_project_settings(cx, |project_settings| {
15415        project_settings.lsp.insert(
15416            language_server_name.into(),
15417            LspSettings {
15418                binary: None,
15419                settings: None,
15420                initialization_options: Some(json!({
15421                    "anotherInitValue": false
15422                })),
15423                enable_lsp_tasks: false,
15424            },
15425        );
15426    });
15427    cx.executor().run_until_parked();
15428    assert_eq!(
15429        server_restarts.load(atomic::Ordering::Acquire),
15430        1,
15431        "Should not restart LSP server on a related LSP settings change that is the same"
15432    );
15433
15434    update_test_project_settings(cx, |project_settings| {
15435        project_settings.lsp.insert(
15436            language_server_name.into(),
15437            LspSettings {
15438                binary: None,
15439                settings: None,
15440                initialization_options: None,
15441                enable_lsp_tasks: false,
15442            },
15443        );
15444    });
15445    cx.executor().run_until_parked();
15446    assert_eq!(
15447        server_restarts.load(atomic::Ordering::Acquire),
15448        2,
15449        "Should restart LSP server on another related LSP settings change"
15450    );
15451}
15452
15453#[gpui::test]
15454async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15455    init_test(cx, |_| {});
15456
15457    let mut cx = EditorLspTestContext::new_rust(
15458        lsp::ServerCapabilities {
15459            completion_provider: Some(lsp::CompletionOptions {
15460                trigger_characters: Some(vec![".".to_string()]),
15461                resolve_provider: Some(true),
15462                ..Default::default()
15463            }),
15464            ..Default::default()
15465        },
15466        cx,
15467    )
15468    .await;
15469
15470    cx.set_state("fn main() { let a = 2ˇ; }");
15471    cx.simulate_keystroke(".");
15472    let completion_item = lsp::CompletionItem {
15473        label: "some".into(),
15474        kind: Some(lsp::CompletionItemKind::SNIPPET),
15475        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15476        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15477            kind: lsp::MarkupKind::Markdown,
15478            value: "```rust\nSome(2)\n```".to_string(),
15479        })),
15480        deprecated: Some(false),
15481        sort_text: Some("fffffff2".to_string()),
15482        filter_text: Some("some".to_string()),
15483        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15484        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15485            range: lsp::Range {
15486                start: lsp::Position {
15487                    line: 0,
15488                    character: 22,
15489                },
15490                end: lsp::Position {
15491                    line: 0,
15492                    character: 22,
15493                },
15494            },
15495            new_text: "Some(2)".to_string(),
15496        })),
15497        additional_text_edits: Some(vec![lsp::TextEdit {
15498            range: lsp::Range {
15499                start: lsp::Position {
15500                    line: 0,
15501                    character: 20,
15502                },
15503                end: lsp::Position {
15504                    line: 0,
15505                    character: 22,
15506                },
15507            },
15508            new_text: "".to_string(),
15509        }]),
15510        ..Default::default()
15511    };
15512
15513    let closure_completion_item = completion_item.clone();
15514    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15515        let task_completion_item = closure_completion_item.clone();
15516        async move {
15517            Ok(Some(lsp::CompletionResponse::Array(vec![
15518                task_completion_item,
15519            ])))
15520        }
15521    });
15522
15523    request.next().await;
15524
15525    cx.condition(|editor, _| editor.context_menu_visible())
15526        .await;
15527    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15528        editor
15529            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15530            .unwrap()
15531    });
15532    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15533
15534    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15535        let task_completion_item = completion_item.clone();
15536        async move { Ok(task_completion_item) }
15537    })
15538    .next()
15539    .await
15540    .unwrap();
15541    apply_additional_edits.await.unwrap();
15542    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15543}
15544
15545#[gpui::test]
15546async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15547    init_test(cx, |_| {});
15548
15549    let mut cx = EditorLspTestContext::new_rust(
15550        lsp::ServerCapabilities {
15551            completion_provider: Some(lsp::CompletionOptions {
15552                trigger_characters: Some(vec![".".to_string()]),
15553                resolve_provider: Some(true),
15554                ..Default::default()
15555            }),
15556            ..Default::default()
15557        },
15558        cx,
15559    )
15560    .await;
15561
15562    cx.set_state("fn main() { let a = 2ˇ; }");
15563    cx.simulate_keystroke(".");
15564
15565    let item1 = lsp::CompletionItem {
15566        label: "method id()".to_string(),
15567        filter_text: Some("id".to_string()),
15568        detail: None,
15569        documentation: None,
15570        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15571            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15572            new_text: ".id".to_string(),
15573        })),
15574        ..lsp::CompletionItem::default()
15575    };
15576
15577    let item2 = lsp::CompletionItem {
15578        label: "other".to_string(),
15579        filter_text: Some("other".to_string()),
15580        detail: None,
15581        documentation: None,
15582        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15583            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15584            new_text: ".other".to_string(),
15585        })),
15586        ..lsp::CompletionItem::default()
15587    };
15588
15589    let item1 = item1.clone();
15590    cx.set_request_handler::<lsp::request::Completion, _, _>({
15591        let item1 = item1.clone();
15592        move |_, _, _| {
15593            let item1 = item1.clone();
15594            let item2 = item2.clone();
15595            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15596        }
15597    })
15598    .next()
15599    .await;
15600
15601    cx.condition(|editor, _| editor.context_menu_visible())
15602        .await;
15603    cx.update_editor(|editor, _, _| {
15604        let context_menu = editor.context_menu.borrow_mut();
15605        let context_menu = context_menu
15606            .as_ref()
15607            .expect("Should have the context menu deployed");
15608        match context_menu {
15609            CodeContextMenu::Completions(completions_menu) => {
15610                let completions = completions_menu.completions.borrow_mut();
15611                assert_eq!(
15612                    completions
15613                        .iter()
15614                        .map(|completion| &completion.label.text)
15615                        .collect::<Vec<_>>(),
15616                    vec!["method id()", "other"]
15617                )
15618            }
15619            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15620        }
15621    });
15622
15623    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15624        let item1 = item1.clone();
15625        move |_, item_to_resolve, _| {
15626            let item1 = item1.clone();
15627            async move {
15628                if item1 == item_to_resolve {
15629                    Ok(lsp::CompletionItem {
15630                        label: "method id()".to_string(),
15631                        filter_text: Some("id".to_string()),
15632                        detail: Some("Now resolved!".to_string()),
15633                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15634                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15635                            range: lsp::Range::new(
15636                                lsp::Position::new(0, 22),
15637                                lsp::Position::new(0, 22),
15638                            ),
15639                            new_text: ".id".to_string(),
15640                        })),
15641                        ..lsp::CompletionItem::default()
15642                    })
15643                } else {
15644                    Ok(item_to_resolve)
15645                }
15646            }
15647        }
15648    })
15649    .next()
15650    .await
15651    .unwrap();
15652    cx.run_until_parked();
15653
15654    cx.update_editor(|editor, window, cx| {
15655        editor.context_menu_next(&Default::default(), window, cx);
15656    });
15657
15658    cx.update_editor(|editor, _, _| {
15659        let context_menu = editor.context_menu.borrow_mut();
15660        let context_menu = context_menu
15661            .as_ref()
15662            .expect("Should have the context menu deployed");
15663        match context_menu {
15664            CodeContextMenu::Completions(completions_menu) => {
15665                let completions = completions_menu.completions.borrow_mut();
15666                assert_eq!(
15667                    completions
15668                        .iter()
15669                        .map(|completion| &completion.label.text)
15670                        .collect::<Vec<_>>(),
15671                    vec!["method id() Now resolved!", "other"],
15672                    "Should update first completion label, but not second as the filter text did not match."
15673                );
15674            }
15675            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15676        }
15677    });
15678}
15679
15680#[gpui::test]
15681async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15682    init_test(cx, |_| {});
15683    let mut cx = EditorLspTestContext::new_rust(
15684        lsp::ServerCapabilities {
15685            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15686            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15687            completion_provider: Some(lsp::CompletionOptions {
15688                resolve_provider: Some(true),
15689                ..Default::default()
15690            }),
15691            ..Default::default()
15692        },
15693        cx,
15694    )
15695    .await;
15696    cx.set_state(indoc! {"
15697        struct TestStruct {
15698            field: i32
15699        }
15700
15701        fn mainˇ() {
15702            let unused_var = 42;
15703            let test_struct = TestStruct { field: 42 };
15704        }
15705    "});
15706    let symbol_range = cx.lsp_range(indoc! {"
15707        struct TestStruct {
15708            field: i32
15709        }
15710
15711        «fn main»() {
15712            let unused_var = 42;
15713            let test_struct = TestStruct { field: 42 };
15714        }
15715    "});
15716    let mut hover_requests =
15717        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15718            Ok(Some(lsp::Hover {
15719                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15720                    kind: lsp::MarkupKind::Markdown,
15721                    value: "Function documentation".to_string(),
15722                }),
15723                range: Some(symbol_range),
15724            }))
15725        });
15726
15727    // Case 1: Test that code action menu hide hover popover
15728    cx.dispatch_action(Hover);
15729    hover_requests.next().await;
15730    cx.condition(|editor, _| editor.hover_state.visible()).await;
15731    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15732        move |_, _, _| async move {
15733            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15734                lsp::CodeAction {
15735                    title: "Remove unused variable".to_string(),
15736                    kind: Some(CodeActionKind::QUICKFIX),
15737                    edit: Some(lsp::WorkspaceEdit {
15738                        changes: Some(
15739                            [(
15740                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15741                                vec![lsp::TextEdit {
15742                                    range: lsp::Range::new(
15743                                        lsp::Position::new(5, 4),
15744                                        lsp::Position::new(5, 27),
15745                                    ),
15746                                    new_text: "".to_string(),
15747                                }],
15748                            )]
15749                            .into_iter()
15750                            .collect(),
15751                        ),
15752                        ..Default::default()
15753                    }),
15754                    ..Default::default()
15755                },
15756            )]))
15757        },
15758    );
15759    cx.update_editor(|editor, window, cx| {
15760        editor.toggle_code_actions(
15761            &ToggleCodeActions {
15762                deployed_from: None,
15763                quick_launch: false,
15764            },
15765            window,
15766            cx,
15767        );
15768    });
15769    code_action_requests.next().await;
15770    cx.run_until_parked();
15771    cx.condition(|editor, _| editor.context_menu_visible())
15772        .await;
15773    cx.update_editor(|editor, _, _| {
15774        assert!(
15775            !editor.hover_state.visible(),
15776            "Hover popover should be hidden when code action menu is shown"
15777        );
15778        // Hide code actions
15779        editor.context_menu.take();
15780    });
15781
15782    // Case 2: Test that code completions hide hover popover
15783    cx.dispatch_action(Hover);
15784    hover_requests.next().await;
15785    cx.condition(|editor, _| editor.hover_state.visible()).await;
15786    let counter = Arc::new(AtomicUsize::new(0));
15787    let mut completion_requests =
15788        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15789            let counter = counter.clone();
15790            async move {
15791                counter.fetch_add(1, atomic::Ordering::Release);
15792                Ok(Some(lsp::CompletionResponse::Array(vec![
15793                    lsp::CompletionItem {
15794                        label: "main".into(),
15795                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15796                        detail: Some("() -> ()".to_string()),
15797                        ..Default::default()
15798                    },
15799                    lsp::CompletionItem {
15800                        label: "TestStruct".into(),
15801                        kind: Some(lsp::CompletionItemKind::STRUCT),
15802                        detail: Some("struct TestStruct".to_string()),
15803                        ..Default::default()
15804                    },
15805                ])))
15806            }
15807        });
15808    cx.update_editor(|editor, window, cx| {
15809        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15810    });
15811    completion_requests.next().await;
15812    cx.condition(|editor, _| editor.context_menu_visible())
15813        .await;
15814    cx.update_editor(|editor, _, _| {
15815        assert!(
15816            !editor.hover_state.visible(),
15817            "Hover popover should be hidden when completion menu is shown"
15818        );
15819    });
15820}
15821
15822#[gpui::test]
15823async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15824    init_test(cx, |_| {});
15825
15826    let mut cx = EditorLspTestContext::new_rust(
15827        lsp::ServerCapabilities {
15828            completion_provider: Some(lsp::CompletionOptions {
15829                trigger_characters: Some(vec![".".to_string()]),
15830                resolve_provider: Some(true),
15831                ..Default::default()
15832            }),
15833            ..Default::default()
15834        },
15835        cx,
15836    )
15837    .await;
15838
15839    cx.set_state("fn main() { let a = 2ˇ; }");
15840    cx.simulate_keystroke(".");
15841
15842    let unresolved_item_1 = lsp::CompletionItem {
15843        label: "id".to_string(),
15844        filter_text: Some("id".to_string()),
15845        detail: None,
15846        documentation: None,
15847        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15848            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15849            new_text: ".id".to_string(),
15850        })),
15851        ..lsp::CompletionItem::default()
15852    };
15853    let resolved_item_1 = lsp::CompletionItem {
15854        additional_text_edits: Some(vec![lsp::TextEdit {
15855            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15856            new_text: "!!".to_string(),
15857        }]),
15858        ..unresolved_item_1.clone()
15859    };
15860    let unresolved_item_2 = lsp::CompletionItem {
15861        label: "other".to_string(),
15862        filter_text: Some("other".to_string()),
15863        detail: None,
15864        documentation: None,
15865        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15866            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15867            new_text: ".other".to_string(),
15868        })),
15869        ..lsp::CompletionItem::default()
15870    };
15871    let resolved_item_2 = lsp::CompletionItem {
15872        additional_text_edits: Some(vec![lsp::TextEdit {
15873            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15874            new_text: "??".to_string(),
15875        }]),
15876        ..unresolved_item_2.clone()
15877    };
15878
15879    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15880    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15881    cx.lsp
15882        .server
15883        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15884            let unresolved_item_1 = unresolved_item_1.clone();
15885            let resolved_item_1 = resolved_item_1.clone();
15886            let unresolved_item_2 = unresolved_item_2.clone();
15887            let resolved_item_2 = resolved_item_2.clone();
15888            let resolve_requests_1 = resolve_requests_1.clone();
15889            let resolve_requests_2 = resolve_requests_2.clone();
15890            move |unresolved_request, _| {
15891                let unresolved_item_1 = unresolved_item_1.clone();
15892                let resolved_item_1 = resolved_item_1.clone();
15893                let unresolved_item_2 = unresolved_item_2.clone();
15894                let resolved_item_2 = resolved_item_2.clone();
15895                let resolve_requests_1 = resolve_requests_1.clone();
15896                let resolve_requests_2 = resolve_requests_2.clone();
15897                async move {
15898                    if unresolved_request == unresolved_item_1 {
15899                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15900                        Ok(resolved_item_1.clone())
15901                    } else if unresolved_request == unresolved_item_2 {
15902                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15903                        Ok(resolved_item_2.clone())
15904                    } else {
15905                        panic!("Unexpected completion item {unresolved_request:?}")
15906                    }
15907                }
15908            }
15909        })
15910        .detach();
15911
15912    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15913        let unresolved_item_1 = unresolved_item_1.clone();
15914        let unresolved_item_2 = unresolved_item_2.clone();
15915        async move {
15916            Ok(Some(lsp::CompletionResponse::Array(vec![
15917                unresolved_item_1,
15918                unresolved_item_2,
15919            ])))
15920        }
15921    })
15922    .next()
15923    .await;
15924
15925    cx.condition(|editor, _| editor.context_menu_visible())
15926        .await;
15927    cx.update_editor(|editor, _, _| {
15928        let context_menu = editor.context_menu.borrow_mut();
15929        let context_menu = context_menu
15930            .as_ref()
15931            .expect("Should have the context menu deployed");
15932        match context_menu {
15933            CodeContextMenu::Completions(completions_menu) => {
15934                let completions = completions_menu.completions.borrow_mut();
15935                assert_eq!(
15936                    completions
15937                        .iter()
15938                        .map(|completion| &completion.label.text)
15939                        .collect::<Vec<_>>(),
15940                    vec!["id", "other"]
15941                )
15942            }
15943            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15944        }
15945    });
15946    cx.run_until_parked();
15947
15948    cx.update_editor(|editor, window, cx| {
15949        editor.context_menu_next(&ContextMenuNext, window, cx);
15950    });
15951    cx.run_until_parked();
15952    cx.update_editor(|editor, window, cx| {
15953        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15954    });
15955    cx.run_until_parked();
15956    cx.update_editor(|editor, window, cx| {
15957        editor.context_menu_next(&ContextMenuNext, window, cx);
15958    });
15959    cx.run_until_parked();
15960    cx.update_editor(|editor, window, cx| {
15961        editor
15962            .compose_completion(&ComposeCompletion::default(), window, cx)
15963            .expect("No task returned")
15964    })
15965    .await
15966    .expect("Completion failed");
15967    cx.run_until_parked();
15968
15969    cx.update_editor(|editor, _, cx| {
15970        assert_eq!(
15971            resolve_requests_1.load(atomic::Ordering::Acquire),
15972            1,
15973            "Should always resolve once despite multiple selections"
15974        );
15975        assert_eq!(
15976            resolve_requests_2.load(atomic::Ordering::Acquire),
15977            1,
15978            "Should always resolve once after multiple selections and applying the completion"
15979        );
15980        assert_eq!(
15981            editor.text(cx),
15982            "fn main() { let a = ??.other; }",
15983            "Should use resolved data when applying the completion"
15984        );
15985    });
15986}
15987
15988#[gpui::test]
15989async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15990    init_test(cx, |_| {});
15991
15992    let item_0 = lsp::CompletionItem {
15993        label: "abs".into(),
15994        insert_text: Some("abs".into()),
15995        data: Some(json!({ "very": "special"})),
15996        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15997        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15998            lsp::InsertReplaceEdit {
15999                new_text: "abs".to_string(),
16000                insert: lsp::Range::default(),
16001                replace: lsp::Range::default(),
16002            },
16003        )),
16004        ..lsp::CompletionItem::default()
16005    };
16006    let items = iter::once(item_0.clone())
16007        .chain((11..51).map(|i| lsp::CompletionItem {
16008            label: format!("item_{}", i),
16009            insert_text: Some(format!("item_{}", i)),
16010            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16011            ..lsp::CompletionItem::default()
16012        }))
16013        .collect::<Vec<_>>();
16014
16015    let default_commit_characters = vec!["?".to_string()];
16016    let default_data = json!({ "default": "data"});
16017    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16018    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16019    let default_edit_range = lsp::Range {
16020        start: lsp::Position {
16021            line: 0,
16022            character: 5,
16023        },
16024        end: lsp::Position {
16025            line: 0,
16026            character: 5,
16027        },
16028    };
16029
16030    let mut cx = EditorLspTestContext::new_rust(
16031        lsp::ServerCapabilities {
16032            completion_provider: Some(lsp::CompletionOptions {
16033                trigger_characters: Some(vec![".".to_string()]),
16034                resolve_provider: Some(true),
16035                ..Default::default()
16036            }),
16037            ..Default::default()
16038        },
16039        cx,
16040    )
16041    .await;
16042
16043    cx.set_state("fn main() { let a = 2ˇ; }");
16044    cx.simulate_keystroke(".");
16045
16046    let completion_data = default_data.clone();
16047    let completion_characters = default_commit_characters.clone();
16048    let completion_items = items.clone();
16049    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16050        let default_data = completion_data.clone();
16051        let default_commit_characters = completion_characters.clone();
16052        let items = completion_items.clone();
16053        async move {
16054            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16055                items,
16056                item_defaults: Some(lsp::CompletionListItemDefaults {
16057                    data: Some(default_data.clone()),
16058                    commit_characters: Some(default_commit_characters.clone()),
16059                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16060                        default_edit_range,
16061                    )),
16062                    insert_text_format: Some(default_insert_text_format),
16063                    insert_text_mode: Some(default_insert_text_mode),
16064                }),
16065                ..lsp::CompletionList::default()
16066            })))
16067        }
16068    })
16069    .next()
16070    .await;
16071
16072    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16073    cx.lsp
16074        .server
16075        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16076            let closure_resolved_items = resolved_items.clone();
16077            move |item_to_resolve, _| {
16078                let closure_resolved_items = closure_resolved_items.clone();
16079                async move {
16080                    closure_resolved_items.lock().push(item_to_resolve.clone());
16081                    Ok(item_to_resolve)
16082                }
16083            }
16084        })
16085        .detach();
16086
16087    cx.condition(|editor, _| editor.context_menu_visible())
16088        .await;
16089    cx.run_until_parked();
16090    cx.update_editor(|editor, _, _| {
16091        let menu = editor.context_menu.borrow_mut();
16092        match menu.as_ref().expect("should have the completions menu") {
16093            CodeContextMenu::Completions(completions_menu) => {
16094                assert_eq!(
16095                    completions_menu
16096                        .entries
16097                        .borrow()
16098                        .iter()
16099                        .map(|mat| mat.string.clone())
16100                        .collect::<Vec<String>>(),
16101                    items
16102                        .iter()
16103                        .map(|completion| completion.label.clone())
16104                        .collect::<Vec<String>>()
16105                );
16106            }
16107            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16108        }
16109    });
16110    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16111    // with 4 from the end.
16112    assert_eq!(
16113        *resolved_items.lock(),
16114        [&items[0..16], &items[items.len() - 4..items.len()]]
16115            .concat()
16116            .iter()
16117            .cloned()
16118            .map(|mut item| {
16119                if item.data.is_none() {
16120                    item.data = Some(default_data.clone());
16121                }
16122                item
16123            })
16124            .collect::<Vec<lsp::CompletionItem>>(),
16125        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16126    );
16127    resolved_items.lock().clear();
16128
16129    cx.update_editor(|editor, window, cx| {
16130        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16131    });
16132    cx.run_until_parked();
16133    // Completions that have already been resolved are skipped.
16134    assert_eq!(
16135        *resolved_items.lock(),
16136        items[items.len() - 17..items.len() - 4]
16137            .iter()
16138            .cloned()
16139            .map(|mut item| {
16140                if item.data.is_none() {
16141                    item.data = Some(default_data.clone());
16142                }
16143                item
16144            })
16145            .collect::<Vec<lsp::CompletionItem>>()
16146    );
16147    resolved_items.lock().clear();
16148}
16149
16150#[gpui::test]
16151async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16152    init_test(cx, |_| {});
16153
16154    let mut cx = EditorLspTestContext::new(
16155        Language::new(
16156            LanguageConfig {
16157                matcher: LanguageMatcher {
16158                    path_suffixes: vec!["jsx".into()],
16159                    ..Default::default()
16160                },
16161                overrides: [(
16162                    "element".into(),
16163                    LanguageConfigOverride {
16164                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16165                        ..Default::default()
16166                    },
16167                )]
16168                .into_iter()
16169                .collect(),
16170                ..Default::default()
16171            },
16172            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16173        )
16174        .with_override_query("(jsx_self_closing_element) @element")
16175        .unwrap(),
16176        lsp::ServerCapabilities {
16177            completion_provider: Some(lsp::CompletionOptions {
16178                trigger_characters: Some(vec![":".to_string()]),
16179                ..Default::default()
16180            }),
16181            ..Default::default()
16182        },
16183        cx,
16184    )
16185    .await;
16186
16187    cx.lsp
16188        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16189            Ok(Some(lsp::CompletionResponse::Array(vec![
16190                lsp::CompletionItem {
16191                    label: "bg-blue".into(),
16192                    ..Default::default()
16193                },
16194                lsp::CompletionItem {
16195                    label: "bg-red".into(),
16196                    ..Default::default()
16197                },
16198                lsp::CompletionItem {
16199                    label: "bg-yellow".into(),
16200                    ..Default::default()
16201                },
16202            ])))
16203        });
16204
16205    cx.set_state(r#"<p class="bgˇ" />"#);
16206
16207    // Trigger completion when typing a dash, because the dash is an extra
16208    // word character in the 'element' scope, which contains the cursor.
16209    cx.simulate_keystroke("-");
16210    cx.executor().run_until_parked();
16211    cx.update_editor(|editor, _, _| {
16212        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16213        {
16214            assert_eq!(
16215                completion_menu_entries(&menu),
16216                &["bg-blue", "bg-red", "bg-yellow"]
16217            );
16218        } else {
16219            panic!("expected completion menu to be open");
16220        }
16221    });
16222
16223    cx.simulate_keystroke("l");
16224    cx.executor().run_until_parked();
16225    cx.update_editor(|editor, _, _| {
16226        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16227        {
16228            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16229        } else {
16230            panic!("expected completion menu to be open");
16231        }
16232    });
16233
16234    // When filtering completions, consider the character after the '-' to
16235    // be the start of a subword.
16236    cx.set_state(r#"<p class="yelˇ" />"#);
16237    cx.simulate_keystroke("l");
16238    cx.executor().run_until_parked();
16239    cx.update_editor(|editor, _, _| {
16240        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16241        {
16242            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16243        } else {
16244            panic!("expected completion menu to be open");
16245        }
16246    });
16247}
16248
16249fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16250    let entries = menu.entries.borrow();
16251    entries.iter().map(|mat| mat.string.clone()).collect()
16252}
16253
16254#[gpui::test]
16255async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16256    init_test(cx, |settings| {
16257        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16258            Formatter::Prettier,
16259        )))
16260    });
16261
16262    let fs = FakeFs::new(cx.executor());
16263    fs.insert_file(path!("/file.ts"), Default::default()).await;
16264
16265    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16266    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16267
16268    language_registry.add(Arc::new(Language::new(
16269        LanguageConfig {
16270            name: "TypeScript".into(),
16271            matcher: LanguageMatcher {
16272                path_suffixes: vec!["ts".to_string()],
16273                ..Default::default()
16274            },
16275            ..Default::default()
16276        },
16277        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16278    )));
16279    update_test_language_settings(cx, |settings| {
16280        settings.defaults.prettier = Some(PrettierSettings {
16281            allowed: true,
16282            ..PrettierSettings::default()
16283        });
16284    });
16285
16286    let test_plugin = "test_plugin";
16287    let _ = language_registry.register_fake_lsp(
16288        "TypeScript",
16289        FakeLspAdapter {
16290            prettier_plugins: vec![test_plugin],
16291            ..Default::default()
16292        },
16293    );
16294
16295    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16296    let buffer = project
16297        .update(cx, |project, cx| {
16298            project.open_local_buffer(path!("/file.ts"), cx)
16299        })
16300        .await
16301        .unwrap();
16302
16303    let buffer_text = "one\ntwo\nthree\n";
16304    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16305    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16306    editor.update_in(cx, |editor, window, cx| {
16307        editor.set_text(buffer_text, window, cx)
16308    });
16309
16310    editor
16311        .update_in(cx, |editor, window, cx| {
16312            editor.perform_format(
16313                project.clone(),
16314                FormatTrigger::Manual,
16315                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16316                window,
16317                cx,
16318            )
16319        })
16320        .unwrap()
16321        .await;
16322    assert_eq!(
16323        editor.update(cx, |editor, cx| editor.text(cx)),
16324        buffer_text.to_string() + prettier_format_suffix,
16325        "Test prettier formatting was not applied to the original buffer text",
16326    );
16327
16328    update_test_language_settings(cx, |settings| {
16329        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16330    });
16331    let format = editor.update_in(cx, |editor, window, cx| {
16332        editor.perform_format(
16333            project.clone(),
16334            FormatTrigger::Manual,
16335            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16336            window,
16337            cx,
16338        )
16339    });
16340    format.await.unwrap();
16341    assert_eq!(
16342        editor.update(cx, |editor, cx| editor.text(cx)),
16343        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16344        "Autoformatting (via test prettier) was not applied to the original buffer text",
16345    );
16346}
16347
16348#[gpui::test]
16349async fn test_addition_reverts(cx: &mut TestAppContext) {
16350    init_test(cx, |_| {});
16351    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16352    let base_text = indoc! {r#"
16353        struct Row;
16354        struct Row1;
16355        struct Row2;
16356
16357        struct Row4;
16358        struct Row5;
16359        struct Row6;
16360
16361        struct Row8;
16362        struct Row9;
16363        struct Row10;"#};
16364
16365    // When addition hunks are not adjacent to carets, no hunk revert is performed
16366    assert_hunk_revert(
16367        indoc! {r#"struct Row;
16368                   struct Row1;
16369                   struct Row1.1;
16370                   struct Row1.2;
16371                   struct Row2;ˇ
16372
16373                   struct Row4;
16374                   struct Row5;
16375                   struct Row6;
16376
16377                   struct Row8;
16378                   ˇstruct Row9;
16379                   struct Row9.1;
16380                   struct Row9.2;
16381                   struct Row9.3;
16382                   struct Row10;"#},
16383        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16384        indoc! {r#"struct Row;
16385                   struct Row1;
16386                   struct Row1.1;
16387                   struct Row1.2;
16388                   struct Row2;ˇ
16389
16390                   struct Row4;
16391                   struct Row5;
16392                   struct Row6;
16393
16394                   struct Row8;
16395                   ˇstruct Row9;
16396                   struct Row9.1;
16397                   struct Row9.2;
16398                   struct Row9.3;
16399                   struct Row10;"#},
16400        base_text,
16401        &mut cx,
16402    );
16403    // Same for selections
16404    assert_hunk_revert(
16405        indoc! {r#"struct Row;
16406                   struct Row1;
16407                   struct Row2;
16408                   struct Row2.1;
16409                   struct Row2.2;
16410                   «ˇ
16411                   struct Row4;
16412                   struct» Row5;
16413                   «struct Row6;
16414                   ˇ»
16415                   struct Row9.1;
16416                   struct Row9.2;
16417                   struct Row9.3;
16418                   struct Row8;
16419                   struct Row9;
16420                   struct Row10;"#},
16421        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16422        indoc! {r#"struct Row;
16423                   struct Row1;
16424                   struct Row2;
16425                   struct Row2.1;
16426                   struct Row2.2;
16427                   «ˇ
16428                   struct Row4;
16429                   struct» Row5;
16430                   «struct Row6;
16431                   ˇ»
16432                   struct Row9.1;
16433                   struct Row9.2;
16434                   struct Row9.3;
16435                   struct Row8;
16436                   struct Row9;
16437                   struct Row10;"#},
16438        base_text,
16439        &mut cx,
16440    );
16441
16442    // When carets and selections intersect the addition hunks, those are reverted.
16443    // Adjacent carets got merged.
16444    assert_hunk_revert(
16445        indoc! {r#"struct Row;
16446                   ˇ// something on the top
16447                   struct Row1;
16448                   struct Row2;
16449                   struct Roˇw3.1;
16450                   struct Row2.2;
16451                   struct Row2.3;ˇ
16452
16453                   struct Row4;
16454                   struct ˇRow5.1;
16455                   struct Row5.2;
16456                   struct «Rowˇ»5.3;
16457                   struct Row5;
16458                   struct Row6;
16459                   ˇ
16460                   struct Row9.1;
16461                   struct «Rowˇ»9.2;
16462                   struct «ˇRow»9.3;
16463                   struct Row8;
16464                   struct Row9;
16465                   «ˇ// something on bottom»
16466                   struct Row10;"#},
16467        vec![
16468            DiffHunkStatusKind::Added,
16469            DiffHunkStatusKind::Added,
16470            DiffHunkStatusKind::Added,
16471            DiffHunkStatusKind::Added,
16472            DiffHunkStatusKind::Added,
16473        ],
16474        indoc! {r#"struct Row;
16475                   ˇstruct Row1;
16476                   struct Row2;
16477                   ˇ
16478                   struct Row4;
16479                   ˇstruct Row5;
16480                   struct Row6;
16481                   ˇ
16482                   ˇstruct Row8;
16483                   struct Row9;
16484                   ˇstruct Row10;"#},
16485        base_text,
16486        &mut cx,
16487    );
16488}
16489
16490#[gpui::test]
16491async fn test_modification_reverts(cx: &mut TestAppContext) {
16492    init_test(cx, |_| {});
16493    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16494    let base_text = indoc! {r#"
16495        struct Row;
16496        struct Row1;
16497        struct Row2;
16498
16499        struct Row4;
16500        struct Row5;
16501        struct Row6;
16502
16503        struct Row8;
16504        struct Row9;
16505        struct Row10;"#};
16506
16507    // Modification hunks behave the same as the addition ones.
16508    assert_hunk_revert(
16509        indoc! {r#"struct Row;
16510                   struct Row1;
16511                   struct Row33;
16512                   ˇ
16513                   struct Row4;
16514                   struct Row5;
16515                   struct Row6;
16516                   ˇ
16517                   struct Row99;
16518                   struct Row9;
16519                   struct Row10;"#},
16520        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16521        indoc! {r#"struct Row;
16522                   struct Row1;
16523                   struct Row33;
16524                   ˇ
16525                   struct Row4;
16526                   struct Row5;
16527                   struct Row6;
16528                   ˇ
16529                   struct Row99;
16530                   struct Row9;
16531                   struct Row10;"#},
16532        base_text,
16533        &mut cx,
16534    );
16535    assert_hunk_revert(
16536        indoc! {r#"struct Row;
16537                   struct Row1;
16538                   struct Row33;
16539                   «ˇ
16540                   struct Row4;
16541                   struct» Row5;
16542                   «struct Row6;
16543                   ˇ»
16544                   struct Row99;
16545                   struct Row9;
16546                   struct Row10;"#},
16547        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16548        indoc! {r#"struct Row;
16549                   struct Row1;
16550                   struct Row33;
16551                   «ˇ
16552                   struct Row4;
16553                   struct» Row5;
16554                   «struct Row6;
16555                   ˇ»
16556                   struct Row99;
16557                   struct Row9;
16558                   struct Row10;"#},
16559        base_text,
16560        &mut cx,
16561    );
16562
16563    assert_hunk_revert(
16564        indoc! {r#"ˇstruct Row1.1;
16565                   struct Row1;
16566                   «ˇstr»uct Row22;
16567
16568                   struct ˇRow44;
16569                   struct Row5;
16570                   struct «Rˇ»ow66;ˇ
16571
16572                   «struˇ»ct Row88;
16573                   struct Row9;
16574                   struct Row1011;ˇ"#},
16575        vec![
16576            DiffHunkStatusKind::Modified,
16577            DiffHunkStatusKind::Modified,
16578            DiffHunkStatusKind::Modified,
16579            DiffHunkStatusKind::Modified,
16580            DiffHunkStatusKind::Modified,
16581            DiffHunkStatusKind::Modified,
16582        ],
16583        indoc! {r#"struct Row;
16584                   ˇstruct Row1;
16585                   struct Row2;
16586                   ˇ
16587                   struct Row4;
16588                   ˇstruct Row5;
16589                   struct Row6;
16590                   ˇ
16591                   struct Row8;
16592                   ˇstruct Row9;
16593                   struct Row10;ˇ"#},
16594        base_text,
16595        &mut cx,
16596    );
16597}
16598
16599#[gpui::test]
16600async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16601    init_test(cx, |_| {});
16602    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16603    let base_text = indoc! {r#"
16604        one
16605
16606        two
16607        three
16608        "#};
16609
16610    cx.set_head_text(base_text);
16611    cx.set_state("\nˇ\n");
16612    cx.executor().run_until_parked();
16613    cx.update_editor(|editor, _window, cx| {
16614        editor.expand_selected_diff_hunks(cx);
16615    });
16616    cx.executor().run_until_parked();
16617    cx.update_editor(|editor, window, cx| {
16618        editor.backspace(&Default::default(), window, cx);
16619    });
16620    cx.run_until_parked();
16621    cx.assert_state_with_diff(
16622        indoc! {r#"
16623
16624        - two
16625        - threeˇ
16626        +
16627        "#}
16628        .to_string(),
16629    );
16630}
16631
16632#[gpui::test]
16633async fn test_deletion_reverts(cx: &mut TestAppContext) {
16634    init_test(cx, |_| {});
16635    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16636    let base_text = indoc! {r#"struct Row;
16637struct Row1;
16638struct Row2;
16639
16640struct Row4;
16641struct Row5;
16642struct Row6;
16643
16644struct Row8;
16645struct Row9;
16646struct Row10;"#};
16647
16648    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16649    assert_hunk_revert(
16650        indoc! {r#"struct Row;
16651                   struct Row2;
16652
16653                   ˇstruct Row4;
16654                   struct Row5;
16655                   struct Row6;
16656                   ˇ
16657                   struct Row8;
16658                   struct Row10;"#},
16659        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16660        indoc! {r#"struct Row;
16661                   struct Row2;
16662
16663                   ˇstruct Row4;
16664                   struct Row5;
16665                   struct Row6;
16666                   ˇ
16667                   struct Row8;
16668                   struct Row10;"#},
16669        base_text,
16670        &mut cx,
16671    );
16672    assert_hunk_revert(
16673        indoc! {r#"struct Row;
16674                   struct Row2;
16675
16676                   «ˇstruct Row4;
16677                   struct» Row5;
16678                   «struct Row6;
16679                   ˇ»
16680                   struct Row8;
16681                   struct Row10;"#},
16682        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16683        indoc! {r#"struct Row;
16684                   struct Row2;
16685
16686                   «ˇstruct Row4;
16687                   struct» Row5;
16688                   «struct Row6;
16689                   ˇ»
16690                   struct Row8;
16691                   struct Row10;"#},
16692        base_text,
16693        &mut cx,
16694    );
16695
16696    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16697    assert_hunk_revert(
16698        indoc! {r#"struct Row;
16699                   ˇstruct Row2;
16700
16701                   struct Row4;
16702                   struct Row5;
16703                   struct Row6;
16704
16705                   struct Row8;ˇ
16706                   struct Row10;"#},
16707        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16708        indoc! {r#"struct Row;
16709                   struct Row1;
16710                   ˇstruct Row2;
16711
16712                   struct Row4;
16713                   struct Row5;
16714                   struct Row6;
16715
16716                   struct Row8;ˇ
16717                   struct Row9;
16718                   struct Row10;"#},
16719        base_text,
16720        &mut cx,
16721    );
16722    assert_hunk_revert(
16723        indoc! {r#"struct Row;
16724                   struct Row2«ˇ;
16725                   struct Row4;
16726                   struct» Row5;
16727                   «struct Row6;
16728
16729                   struct Row8;ˇ»
16730                   struct Row10;"#},
16731        vec![
16732            DiffHunkStatusKind::Deleted,
16733            DiffHunkStatusKind::Deleted,
16734            DiffHunkStatusKind::Deleted,
16735        ],
16736        indoc! {r#"struct Row;
16737                   struct Row1;
16738                   struct Row2«ˇ;
16739
16740                   struct Row4;
16741                   struct» Row5;
16742                   «struct Row6;
16743
16744                   struct Row8;ˇ»
16745                   struct Row9;
16746                   struct Row10;"#},
16747        base_text,
16748        &mut cx,
16749    );
16750}
16751
16752#[gpui::test]
16753async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16754    init_test(cx, |_| {});
16755
16756    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16757    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16758    let base_text_3 =
16759        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16760
16761    let text_1 = edit_first_char_of_every_line(base_text_1);
16762    let text_2 = edit_first_char_of_every_line(base_text_2);
16763    let text_3 = edit_first_char_of_every_line(base_text_3);
16764
16765    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16766    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16767    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16768
16769    let multibuffer = cx.new(|cx| {
16770        let mut multibuffer = MultiBuffer::new(ReadWrite);
16771        multibuffer.push_excerpts(
16772            buffer_1.clone(),
16773            [
16774                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16775                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16776                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16777            ],
16778            cx,
16779        );
16780        multibuffer.push_excerpts(
16781            buffer_2.clone(),
16782            [
16783                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16784                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16785                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16786            ],
16787            cx,
16788        );
16789        multibuffer.push_excerpts(
16790            buffer_3.clone(),
16791            [
16792                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16793                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16794                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16795            ],
16796            cx,
16797        );
16798        multibuffer
16799    });
16800
16801    let fs = FakeFs::new(cx.executor());
16802    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16803    let (editor, cx) = cx
16804        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16805    editor.update_in(cx, |editor, _window, cx| {
16806        for (buffer, diff_base) in [
16807            (buffer_1.clone(), base_text_1),
16808            (buffer_2.clone(), base_text_2),
16809            (buffer_3.clone(), base_text_3),
16810        ] {
16811            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16812            editor
16813                .buffer
16814                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16815        }
16816    });
16817    cx.executor().run_until_parked();
16818
16819    editor.update_in(cx, |editor, window, cx| {
16820        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}");
16821        editor.select_all(&SelectAll, window, cx);
16822        editor.git_restore(&Default::default(), window, cx);
16823    });
16824    cx.executor().run_until_parked();
16825
16826    // When all ranges are selected, all buffer hunks are reverted.
16827    editor.update(cx, |editor, cx| {
16828        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");
16829    });
16830    buffer_1.update(cx, |buffer, _| {
16831        assert_eq!(buffer.text(), base_text_1);
16832    });
16833    buffer_2.update(cx, |buffer, _| {
16834        assert_eq!(buffer.text(), base_text_2);
16835    });
16836    buffer_3.update(cx, |buffer, _| {
16837        assert_eq!(buffer.text(), base_text_3);
16838    });
16839
16840    editor.update_in(cx, |editor, window, cx| {
16841        editor.undo(&Default::default(), window, cx);
16842    });
16843
16844    editor.update_in(cx, |editor, window, cx| {
16845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16846            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16847        });
16848        editor.git_restore(&Default::default(), window, cx);
16849    });
16850
16851    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16852    // but not affect buffer_2 and its related excerpts.
16853    editor.update(cx, |editor, cx| {
16854        assert_eq!(
16855            editor.text(cx),
16856            "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}"
16857        );
16858    });
16859    buffer_1.update(cx, |buffer, _| {
16860        assert_eq!(buffer.text(), base_text_1);
16861    });
16862    buffer_2.update(cx, |buffer, _| {
16863        assert_eq!(
16864            buffer.text(),
16865            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16866        );
16867    });
16868    buffer_3.update(cx, |buffer, _| {
16869        assert_eq!(
16870            buffer.text(),
16871            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16872        );
16873    });
16874
16875    fn edit_first_char_of_every_line(text: &str) -> String {
16876        text.split('\n')
16877            .map(|line| format!("X{}", &line[1..]))
16878            .collect::<Vec<_>>()
16879            .join("\n")
16880    }
16881}
16882
16883#[gpui::test]
16884async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
16885    init_test(cx, |_| {});
16886
16887    let cols = 4;
16888    let rows = 10;
16889    let sample_text_1 = sample_text(rows, cols, 'a');
16890    assert_eq!(
16891        sample_text_1,
16892        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16893    );
16894    let sample_text_2 = sample_text(rows, cols, 'l');
16895    assert_eq!(
16896        sample_text_2,
16897        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16898    );
16899    let sample_text_3 = sample_text(rows, cols, 'v');
16900    assert_eq!(
16901        sample_text_3,
16902        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16903    );
16904
16905    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16906    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16907    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16908
16909    let multi_buffer = cx.new(|cx| {
16910        let mut multibuffer = MultiBuffer::new(ReadWrite);
16911        multibuffer.push_excerpts(
16912            buffer_1.clone(),
16913            [
16914                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16915                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16916                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16917            ],
16918            cx,
16919        );
16920        multibuffer.push_excerpts(
16921            buffer_2.clone(),
16922            [
16923                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16924                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16925                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16926            ],
16927            cx,
16928        );
16929        multibuffer.push_excerpts(
16930            buffer_3.clone(),
16931            [
16932                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16933                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16934                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16935            ],
16936            cx,
16937        );
16938        multibuffer
16939    });
16940
16941    let fs = FakeFs::new(cx.executor());
16942    fs.insert_tree(
16943        "/a",
16944        json!({
16945            "main.rs": sample_text_1,
16946            "other.rs": sample_text_2,
16947            "lib.rs": sample_text_3,
16948        }),
16949    )
16950    .await;
16951    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16952    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16953    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16954    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16955        Editor::new(
16956            EditorMode::full(),
16957            multi_buffer,
16958            Some(project.clone()),
16959            window,
16960            cx,
16961        )
16962    });
16963    let multibuffer_item_id = workspace
16964        .update(cx, |workspace, window, cx| {
16965            assert!(
16966                workspace.active_item(cx).is_none(),
16967                "active item should be None before the first item is added"
16968            );
16969            workspace.add_item_to_active_pane(
16970                Box::new(multi_buffer_editor.clone()),
16971                None,
16972                true,
16973                window,
16974                cx,
16975            );
16976            let active_item = workspace
16977                .active_item(cx)
16978                .expect("should have an active item after adding the multi buffer");
16979            assert!(
16980                !active_item.is_singleton(cx),
16981                "A multi buffer was expected to active after adding"
16982            );
16983            active_item.item_id()
16984        })
16985        .unwrap();
16986    cx.executor().run_until_parked();
16987
16988    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16989        editor.change_selections(
16990            SelectionEffects::scroll(Autoscroll::Next),
16991            window,
16992            cx,
16993            |s| s.select_ranges(Some(1..2)),
16994        );
16995        editor.open_excerpts(&OpenExcerpts, window, cx);
16996    });
16997    cx.executor().run_until_parked();
16998    let first_item_id = workspace
16999        .update(cx, |workspace, window, cx| {
17000            let active_item = workspace
17001                .active_item(cx)
17002                .expect("should have an active item after navigating into the 1st buffer");
17003            let first_item_id = active_item.item_id();
17004            assert_ne!(
17005                first_item_id, multibuffer_item_id,
17006                "Should navigate into the 1st buffer and activate it"
17007            );
17008            assert!(
17009                active_item.is_singleton(cx),
17010                "New active item should be a singleton buffer"
17011            );
17012            assert_eq!(
17013                active_item
17014                    .act_as::<Editor>(cx)
17015                    .expect("should have navigated into an editor for the 1st buffer")
17016                    .read(cx)
17017                    .text(cx),
17018                sample_text_1
17019            );
17020
17021            workspace
17022                .go_back(workspace.active_pane().downgrade(), window, cx)
17023                .detach_and_log_err(cx);
17024
17025            first_item_id
17026        })
17027        .unwrap();
17028    cx.executor().run_until_parked();
17029    workspace
17030        .update(cx, |workspace, _, cx| {
17031            let active_item = workspace
17032                .active_item(cx)
17033                .expect("should have an active item after navigating back");
17034            assert_eq!(
17035                active_item.item_id(),
17036                multibuffer_item_id,
17037                "Should navigate back to the multi buffer"
17038            );
17039            assert!(!active_item.is_singleton(cx));
17040        })
17041        .unwrap();
17042
17043    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17044        editor.change_selections(
17045            SelectionEffects::scroll(Autoscroll::Next),
17046            window,
17047            cx,
17048            |s| s.select_ranges(Some(39..40)),
17049        );
17050        editor.open_excerpts(&OpenExcerpts, window, cx);
17051    });
17052    cx.executor().run_until_parked();
17053    let second_item_id = workspace
17054        .update(cx, |workspace, window, cx| {
17055            let active_item = workspace
17056                .active_item(cx)
17057                .expect("should have an active item after navigating into the 2nd buffer");
17058            let second_item_id = active_item.item_id();
17059            assert_ne!(
17060                second_item_id, multibuffer_item_id,
17061                "Should navigate away from the multibuffer"
17062            );
17063            assert_ne!(
17064                second_item_id, first_item_id,
17065                "Should navigate into the 2nd buffer and activate it"
17066            );
17067            assert!(
17068                active_item.is_singleton(cx),
17069                "New active item should be a singleton buffer"
17070            );
17071            assert_eq!(
17072                active_item
17073                    .act_as::<Editor>(cx)
17074                    .expect("should have navigated into an editor")
17075                    .read(cx)
17076                    .text(cx),
17077                sample_text_2
17078            );
17079
17080            workspace
17081                .go_back(workspace.active_pane().downgrade(), window, cx)
17082                .detach_and_log_err(cx);
17083
17084            second_item_id
17085        })
17086        .unwrap();
17087    cx.executor().run_until_parked();
17088    workspace
17089        .update(cx, |workspace, _, cx| {
17090            let active_item = workspace
17091                .active_item(cx)
17092                .expect("should have an active item after navigating back from the 2nd buffer");
17093            assert_eq!(
17094                active_item.item_id(),
17095                multibuffer_item_id,
17096                "Should navigate back from the 2nd buffer to the multi buffer"
17097            );
17098            assert!(!active_item.is_singleton(cx));
17099        })
17100        .unwrap();
17101
17102    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17103        editor.change_selections(
17104            SelectionEffects::scroll(Autoscroll::Next),
17105            window,
17106            cx,
17107            |s| s.select_ranges(Some(70..70)),
17108        );
17109        editor.open_excerpts(&OpenExcerpts, window, cx);
17110    });
17111    cx.executor().run_until_parked();
17112    workspace
17113        .update(cx, |workspace, window, cx| {
17114            let active_item = workspace
17115                .active_item(cx)
17116                .expect("should have an active item after navigating into the 3rd buffer");
17117            let third_item_id = active_item.item_id();
17118            assert_ne!(
17119                third_item_id, multibuffer_item_id,
17120                "Should navigate into the 3rd buffer and activate it"
17121            );
17122            assert_ne!(third_item_id, first_item_id);
17123            assert_ne!(third_item_id, second_item_id);
17124            assert!(
17125                active_item.is_singleton(cx),
17126                "New active item should be a singleton buffer"
17127            );
17128            assert_eq!(
17129                active_item
17130                    .act_as::<Editor>(cx)
17131                    .expect("should have navigated into an editor")
17132                    .read(cx)
17133                    .text(cx),
17134                sample_text_3
17135            );
17136
17137            workspace
17138                .go_back(workspace.active_pane().downgrade(), window, cx)
17139                .detach_and_log_err(cx);
17140        })
17141        .unwrap();
17142    cx.executor().run_until_parked();
17143    workspace
17144        .update(cx, |workspace, _, cx| {
17145            let active_item = workspace
17146                .active_item(cx)
17147                .expect("should have an active item after navigating back from the 3rd buffer");
17148            assert_eq!(
17149                active_item.item_id(),
17150                multibuffer_item_id,
17151                "Should navigate back from the 3rd buffer to the multi buffer"
17152            );
17153            assert!(!active_item.is_singleton(cx));
17154        })
17155        .unwrap();
17156}
17157
17158#[gpui::test]
17159async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17160    init_test(cx, |_| {});
17161
17162    let mut cx = EditorTestContext::new(cx).await;
17163
17164    let diff_base = r#"
17165        use some::mod;
17166
17167        const A: u32 = 42;
17168
17169        fn main() {
17170            println!("hello");
17171
17172            println!("world");
17173        }
17174        "#
17175    .unindent();
17176
17177    cx.set_state(
17178        &r#"
17179        use some::modified;
17180
17181        ˇ
17182        fn main() {
17183            println!("hello there");
17184
17185            println!("around the");
17186            println!("world");
17187        }
17188        "#
17189        .unindent(),
17190    );
17191
17192    cx.set_head_text(&diff_base);
17193    executor.run_until_parked();
17194
17195    cx.update_editor(|editor, window, cx| {
17196        editor.go_to_next_hunk(&GoToHunk, window, cx);
17197        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17198    });
17199    executor.run_until_parked();
17200    cx.assert_state_with_diff(
17201        r#"
17202          use some::modified;
17203
17204
17205          fn main() {
17206        -     println!("hello");
17207        + ˇ    println!("hello there");
17208
17209              println!("around the");
17210              println!("world");
17211          }
17212        "#
17213        .unindent(),
17214    );
17215
17216    cx.update_editor(|editor, window, cx| {
17217        for _ in 0..2 {
17218            editor.go_to_next_hunk(&GoToHunk, window, cx);
17219            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17220        }
17221    });
17222    executor.run_until_parked();
17223    cx.assert_state_with_diff(
17224        r#"
17225        - use some::mod;
17226        + ˇuse some::modified;
17227
17228
17229          fn main() {
17230        -     println!("hello");
17231        +     println!("hello there");
17232
17233        +     println!("around the");
17234              println!("world");
17235          }
17236        "#
17237        .unindent(),
17238    );
17239
17240    cx.update_editor(|editor, window, cx| {
17241        editor.go_to_next_hunk(&GoToHunk, window, cx);
17242        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17243    });
17244    executor.run_until_parked();
17245    cx.assert_state_with_diff(
17246        r#"
17247        - use some::mod;
17248        + use some::modified;
17249
17250        - const A: u32 = 42;
17251          ˇ
17252          fn main() {
17253        -     println!("hello");
17254        +     println!("hello there");
17255
17256        +     println!("around the");
17257              println!("world");
17258          }
17259        "#
17260        .unindent(),
17261    );
17262
17263    cx.update_editor(|editor, window, cx| {
17264        editor.cancel(&Cancel, window, cx);
17265    });
17266
17267    cx.assert_state_with_diff(
17268        r#"
17269          use some::modified;
17270
17271          ˇ
17272          fn main() {
17273              println!("hello there");
17274
17275              println!("around the");
17276              println!("world");
17277          }
17278        "#
17279        .unindent(),
17280    );
17281}
17282
17283#[gpui::test]
17284async fn test_diff_base_change_with_expanded_diff_hunks(
17285    executor: BackgroundExecutor,
17286    cx: &mut TestAppContext,
17287) {
17288    init_test(cx, |_| {});
17289
17290    let mut cx = EditorTestContext::new(cx).await;
17291
17292    let diff_base = r#"
17293        use some::mod1;
17294        use some::mod2;
17295
17296        const A: u32 = 42;
17297        const B: u32 = 42;
17298        const C: u32 = 42;
17299
17300        fn main() {
17301            println!("hello");
17302
17303            println!("world");
17304        }
17305        "#
17306    .unindent();
17307
17308    cx.set_state(
17309        &r#"
17310        use some::mod2;
17311
17312        const A: u32 = 42;
17313        const C: u32 = 42;
17314
17315        fn main(ˇ) {
17316            //println!("hello");
17317
17318            println!("world");
17319            //
17320            //
17321        }
17322        "#
17323        .unindent(),
17324    );
17325
17326    cx.set_head_text(&diff_base);
17327    executor.run_until_parked();
17328
17329    cx.update_editor(|editor, window, cx| {
17330        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17331    });
17332    executor.run_until_parked();
17333    cx.assert_state_with_diff(
17334        r#"
17335        - use some::mod1;
17336          use some::mod2;
17337
17338          const A: u32 = 42;
17339        - const B: u32 = 42;
17340          const C: u32 = 42;
17341
17342          fn main(ˇ) {
17343        -     println!("hello");
17344        +     //println!("hello");
17345
17346              println!("world");
17347        +     //
17348        +     //
17349          }
17350        "#
17351        .unindent(),
17352    );
17353
17354    cx.set_head_text("new diff base!");
17355    executor.run_until_parked();
17356    cx.assert_state_with_diff(
17357        r#"
17358        - new diff base!
17359        + use some::mod2;
17360        +
17361        + const A: u32 = 42;
17362        + const C: u32 = 42;
17363        +
17364        + fn main(ˇ) {
17365        +     //println!("hello");
17366        +
17367        +     println!("world");
17368        +     //
17369        +     //
17370        + }
17371        "#
17372        .unindent(),
17373    );
17374}
17375
17376#[gpui::test]
17377async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17378    init_test(cx, |_| {});
17379
17380    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17381    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17382    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17383    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17384    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17385    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17386
17387    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17388    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17389    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17390
17391    let multi_buffer = cx.new(|cx| {
17392        let mut multibuffer = MultiBuffer::new(ReadWrite);
17393        multibuffer.push_excerpts(
17394            buffer_1.clone(),
17395            [
17396                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17397                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17398                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17399            ],
17400            cx,
17401        );
17402        multibuffer.push_excerpts(
17403            buffer_2.clone(),
17404            [
17405                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17406                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17407                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17408            ],
17409            cx,
17410        );
17411        multibuffer.push_excerpts(
17412            buffer_3.clone(),
17413            [
17414                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17415                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17416                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17417            ],
17418            cx,
17419        );
17420        multibuffer
17421    });
17422
17423    let editor =
17424        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17425    editor
17426        .update(cx, |editor, _window, cx| {
17427            for (buffer, diff_base) in [
17428                (buffer_1.clone(), file_1_old),
17429                (buffer_2.clone(), file_2_old),
17430                (buffer_3.clone(), file_3_old),
17431            ] {
17432                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17433                editor
17434                    .buffer
17435                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17436            }
17437        })
17438        .unwrap();
17439
17440    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17441    cx.run_until_parked();
17442
17443    cx.assert_editor_state(
17444        &"
17445            ˇaaa
17446            ccc
17447            ddd
17448
17449            ggg
17450            hhh
17451
17452
17453            lll
17454            mmm
17455            NNN
17456
17457            qqq
17458            rrr
17459
17460            uuu
17461            111
17462            222
17463            333
17464
17465            666
17466            777
17467
17468            000
17469            !!!"
17470        .unindent(),
17471    );
17472
17473    cx.update_editor(|editor, window, cx| {
17474        editor.select_all(&SelectAll, window, cx);
17475        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17476    });
17477    cx.executor().run_until_parked();
17478
17479    cx.assert_state_with_diff(
17480        "
17481            «aaa
17482          - bbb
17483            ccc
17484            ddd
17485
17486            ggg
17487            hhh
17488
17489
17490            lll
17491            mmm
17492          - nnn
17493          + NNN
17494
17495            qqq
17496            rrr
17497
17498            uuu
17499            111
17500            222
17501            333
17502
17503          + 666
17504            777
17505
17506            000
17507            !!!ˇ»"
17508            .unindent(),
17509    );
17510}
17511
17512#[gpui::test]
17513async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17514    init_test(cx, |_| {});
17515
17516    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17517    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17518
17519    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17520    let multi_buffer = cx.new(|cx| {
17521        let mut multibuffer = MultiBuffer::new(ReadWrite);
17522        multibuffer.push_excerpts(
17523            buffer.clone(),
17524            [
17525                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17526                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17527                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17528            ],
17529            cx,
17530        );
17531        multibuffer
17532    });
17533
17534    let editor =
17535        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17536    editor
17537        .update(cx, |editor, _window, cx| {
17538            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17539            editor
17540                .buffer
17541                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17542        })
17543        .unwrap();
17544
17545    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17546    cx.run_until_parked();
17547
17548    cx.update_editor(|editor, window, cx| {
17549        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17550    });
17551    cx.executor().run_until_parked();
17552
17553    // When the start of a hunk coincides with the start of its excerpt,
17554    // the hunk is expanded. When the start of a a hunk is earlier than
17555    // the start of its excerpt, the hunk is not expanded.
17556    cx.assert_state_with_diff(
17557        "
17558            ˇaaa
17559          - bbb
17560          + BBB
17561
17562          - ddd
17563          - eee
17564          + DDD
17565          + EEE
17566            fff
17567
17568            iii
17569        "
17570        .unindent(),
17571    );
17572}
17573
17574#[gpui::test]
17575async fn test_edits_around_expanded_insertion_hunks(
17576    executor: BackgroundExecutor,
17577    cx: &mut TestAppContext,
17578) {
17579    init_test(cx, |_| {});
17580
17581    let mut cx = EditorTestContext::new(cx).await;
17582
17583    let diff_base = r#"
17584        use some::mod1;
17585        use some::mod2;
17586
17587        const A: u32 = 42;
17588
17589        fn main() {
17590            println!("hello");
17591
17592            println!("world");
17593        }
17594        "#
17595    .unindent();
17596    executor.run_until_parked();
17597    cx.set_state(
17598        &r#"
17599        use some::mod1;
17600        use some::mod2;
17601
17602        const A: u32 = 42;
17603        const B: u32 = 42;
17604        const C: u32 = 42;
17605        ˇ
17606
17607        fn main() {
17608            println!("hello");
17609
17610            println!("world");
17611        }
17612        "#
17613        .unindent(),
17614    );
17615
17616    cx.set_head_text(&diff_base);
17617    executor.run_until_parked();
17618
17619    cx.update_editor(|editor, window, cx| {
17620        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17621    });
17622    executor.run_until_parked();
17623
17624    cx.assert_state_with_diff(
17625        r#"
17626        use some::mod1;
17627        use some::mod2;
17628
17629        const A: u32 = 42;
17630      + const B: u32 = 42;
17631      + const C: u32 = 42;
17632      + ˇ
17633
17634        fn main() {
17635            println!("hello");
17636
17637            println!("world");
17638        }
17639      "#
17640        .unindent(),
17641    );
17642
17643    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17644    executor.run_until_parked();
17645
17646    cx.assert_state_with_diff(
17647        r#"
17648        use some::mod1;
17649        use some::mod2;
17650
17651        const A: u32 = 42;
17652      + const B: u32 = 42;
17653      + const C: u32 = 42;
17654      + const D: u32 = 42;
17655      + ˇ
17656
17657        fn main() {
17658            println!("hello");
17659
17660            println!("world");
17661        }
17662      "#
17663        .unindent(),
17664    );
17665
17666    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17667    executor.run_until_parked();
17668
17669    cx.assert_state_with_diff(
17670        r#"
17671        use some::mod1;
17672        use some::mod2;
17673
17674        const A: u32 = 42;
17675      + const B: u32 = 42;
17676      + const C: u32 = 42;
17677      + const D: u32 = 42;
17678      + const E: u32 = 42;
17679      + ˇ
17680
17681        fn main() {
17682            println!("hello");
17683
17684            println!("world");
17685        }
17686      "#
17687        .unindent(),
17688    );
17689
17690    cx.update_editor(|editor, window, cx| {
17691        editor.delete_line(&DeleteLine, window, cx);
17692    });
17693    executor.run_until_parked();
17694
17695    cx.assert_state_with_diff(
17696        r#"
17697        use some::mod1;
17698        use some::mod2;
17699
17700        const A: u32 = 42;
17701      + const B: u32 = 42;
17702      + const C: u32 = 42;
17703      + const D: u32 = 42;
17704      + const E: u32 = 42;
17705        ˇ
17706        fn main() {
17707            println!("hello");
17708
17709            println!("world");
17710        }
17711      "#
17712        .unindent(),
17713    );
17714
17715    cx.update_editor(|editor, window, cx| {
17716        editor.move_up(&MoveUp, window, cx);
17717        editor.delete_line(&DeleteLine, window, cx);
17718        editor.move_up(&MoveUp, window, cx);
17719        editor.delete_line(&DeleteLine, window, cx);
17720        editor.move_up(&MoveUp, window, cx);
17721        editor.delete_line(&DeleteLine, window, cx);
17722    });
17723    executor.run_until_parked();
17724    cx.assert_state_with_diff(
17725        r#"
17726        use some::mod1;
17727        use some::mod2;
17728
17729        const A: u32 = 42;
17730      + const B: u32 = 42;
17731        ˇ
17732        fn main() {
17733            println!("hello");
17734
17735            println!("world");
17736        }
17737      "#
17738        .unindent(),
17739    );
17740
17741    cx.update_editor(|editor, window, cx| {
17742        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17743        editor.delete_line(&DeleteLine, window, cx);
17744    });
17745    executor.run_until_parked();
17746    cx.assert_state_with_diff(
17747        r#"
17748        ˇ
17749        fn main() {
17750            println!("hello");
17751
17752            println!("world");
17753        }
17754      "#
17755        .unindent(),
17756    );
17757}
17758
17759#[gpui::test]
17760async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17761    init_test(cx, |_| {});
17762
17763    let mut cx = EditorTestContext::new(cx).await;
17764    cx.set_head_text(indoc! { "
17765        one
17766        two
17767        three
17768        four
17769        five
17770        "
17771    });
17772    cx.set_state(indoc! { "
17773        one
17774        ˇthree
17775        five
17776    "});
17777    cx.run_until_parked();
17778    cx.update_editor(|editor, window, cx| {
17779        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17780    });
17781    cx.assert_state_with_diff(
17782        indoc! { "
17783        one
17784      - two
17785        ˇthree
17786      - four
17787        five
17788    "}
17789        .to_string(),
17790    );
17791    cx.update_editor(|editor, window, cx| {
17792        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17793    });
17794
17795    cx.assert_state_with_diff(
17796        indoc! { "
17797        one
17798        ˇthree
17799        five
17800    "}
17801        .to_string(),
17802    );
17803
17804    cx.set_state(indoc! { "
17805        one
17806        ˇTWO
17807        three
17808        four
17809        five
17810    "});
17811    cx.run_until_parked();
17812    cx.update_editor(|editor, window, cx| {
17813        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17814    });
17815
17816    cx.assert_state_with_diff(
17817        indoc! { "
17818            one
17819          - two
17820          + ˇTWO
17821            three
17822            four
17823            five
17824        "}
17825        .to_string(),
17826    );
17827    cx.update_editor(|editor, window, cx| {
17828        editor.move_up(&Default::default(), window, cx);
17829        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17830    });
17831    cx.assert_state_with_diff(
17832        indoc! { "
17833            one
17834            ˇTWO
17835            three
17836            four
17837            five
17838        "}
17839        .to_string(),
17840    );
17841}
17842
17843#[gpui::test]
17844async fn test_edits_around_expanded_deletion_hunks(
17845    executor: BackgroundExecutor,
17846    cx: &mut TestAppContext,
17847) {
17848    init_test(cx, |_| {});
17849
17850    let mut cx = EditorTestContext::new(cx).await;
17851
17852    let diff_base = r#"
17853        use some::mod1;
17854        use some::mod2;
17855
17856        const A: u32 = 42;
17857        const B: u32 = 42;
17858        const C: u32 = 42;
17859
17860
17861        fn main() {
17862            println!("hello");
17863
17864            println!("world");
17865        }
17866    "#
17867    .unindent();
17868    executor.run_until_parked();
17869    cx.set_state(
17870        &r#"
17871        use some::mod1;
17872        use some::mod2;
17873
17874        ˇconst B: u32 = 42;
17875        const C: u32 = 42;
17876
17877
17878        fn main() {
17879            println!("hello");
17880
17881            println!("world");
17882        }
17883        "#
17884        .unindent(),
17885    );
17886
17887    cx.set_head_text(&diff_base);
17888    executor.run_until_parked();
17889
17890    cx.update_editor(|editor, window, cx| {
17891        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17892    });
17893    executor.run_until_parked();
17894
17895    cx.assert_state_with_diff(
17896        r#"
17897        use some::mod1;
17898        use some::mod2;
17899
17900      - const A: u32 = 42;
17901        ˇconst B: u32 = 42;
17902        const C: u32 = 42;
17903
17904
17905        fn main() {
17906            println!("hello");
17907
17908            println!("world");
17909        }
17910      "#
17911        .unindent(),
17912    );
17913
17914    cx.update_editor(|editor, window, cx| {
17915        editor.delete_line(&DeleteLine, window, cx);
17916    });
17917    executor.run_until_parked();
17918    cx.assert_state_with_diff(
17919        r#"
17920        use some::mod1;
17921        use some::mod2;
17922
17923      - const A: u32 = 42;
17924      - const B: u32 = 42;
17925        ˇconst C: u32 = 42;
17926
17927
17928        fn main() {
17929            println!("hello");
17930
17931            println!("world");
17932        }
17933      "#
17934        .unindent(),
17935    );
17936
17937    cx.update_editor(|editor, window, cx| {
17938        editor.delete_line(&DeleteLine, window, cx);
17939    });
17940    executor.run_until_parked();
17941    cx.assert_state_with_diff(
17942        r#"
17943        use some::mod1;
17944        use some::mod2;
17945
17946      - const A: u32 = 42;
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.update_editor(|editor, window, cx| {
17961        editor.handle_input("replacement", window, cx);
17962    });
17963    executor.run_until_parked();
17964    cx.assert_state_with_diff(
17965        r#"
17966        use some::mod1;
17967        use some::mod2;
17968
17969      - const A: u32 = 42;
17970      - const B: u32 = 42;
17971      - const C: u32 = 42;
17972      -
17973      + replacementˇ
17974
17975        fn main() {
17976            println!("hello");
17977
17978            println!("world");
17979        }
17980      "#
17981        .unindent(),
17982    );
17983}
17984
17985#[gpui::test]
17986async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17987    init_test(cx, |_| {});
17988
17989    let mut cx = EditorTestContext::new(cx).await;
17990
17991    let base_text = r#"
17992        one
17993        two
17994        three
17995        four
17996        five
17997    "#
17998    .unindent();
17999    executor.run_until_parked();
18000    cx.set_state(
18001        &r#"
18002        one
18003        two
18004        fˇour
18005        five
18006        "#
18007        .unindent(),
18008    );
18009
18010    cx.set_head_text(&base_text);
18011    executor.run_until_parked();
18012
18013    cx.update_editor(|editor, window, cx| {
18014        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18015    });
18016    executor.run_until_parked();
18017
18018    cx.assert_state_with_diff(
18019        r#"
18020          one
18021          two
18022        - three
18023          fˇour
18024          five
18025        "#
18026        .unindent(),
18027    );
18028
18029    cx.update_editor(|editor, window, cx| {
18030        editor.backspace(&Backspace, window, cx);
18031        editor.backspace(&Backspace, window, cx);
18032    });
18033    executor.run_until_parked();
18034    cx.assert_state_with_diff(
18035        r#"
18036          one
18037          two
18038        - threeˇ
18039        - four
18040        + our
18041          five
18042        "#
18043        .unindent(),
18044    );
18045}
18046
18047#[gpui::test]
18048async fn test_edit_after_expanded_modification_hunk(
18049    executor: BackgroundExecutor,
18050    cx: &mut TestAppContext,
18051) {
18052    init_test(cx, |_| {});
18053
18054    let mut cx = EditorTestContext::new(cx).await;
18055
18056    let diff_base = r#"
18057        use some::mod1;
18058        use some::mod2;
18059
18060        const A: u32 = 42;
18061        const B: u32 = 42;
18062        const C: u32 = 42;
18063        const D: u32 = 42;
18064
18065
18066        fn main() {
18067            println!("hello");
18068
18069            println!("world");
18070        }"#
18071    .unindent();
18072
18073    cx.set_state(
18074        &r#"
18075        use some::mod1;
18076        use some::mod2;
18077
18078        const A: u32 = 42;
18079        const B: u32 = 42;
18080        const C: u32 = 43ˇ
18081        const D: u32 = 42;
18082
18083
18084        fn main() {
18085            println!("hello");
18086
18087            println!("world");
18088        }"#
18089        .unindent(),
18090    );
18091
18092    cx.set_head_text(&diff_base);
18093    executor.run_until_parked();
18094    cx.update_editor(|editor, window, cx| {
18095        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18096    });
18097    executor.run_until_parked();
18098
18099    cx.assert_state_with_diff(
18100        r#"
18101        use some::mod1;
18102        use some::mod2;
18103
18104        const A: u32 = 42;
18105        const B: u32 = 42;
18106      - const C: u32 = 42;
18107      + const C: u32 = 43ˇ
18108        const D: u32 = 42;
18109
18110
18111        fn main() {
18112            println!("hello");
18113
18114            println!("world");
18115        }"#
18116        .unindent(),
18117    );
18118
18119    cx.update_editor(|editor, window, cx| {
18120        editor.handle_input("\nnew_line\n", window, cx);
18121    });
18122    executor.run_until_parked();
18123
18124    cx.assert_state_with_diff(
18125        r#"
18126        use some::mod1;
18127        use some::mod2;
18128
18129        const A: u32 = 42;
18130        const B: u32 = 42;
18131      - const C: u32 = 42;
18132      + const C: u32 = 43
18133      + new_line
18134      + ˇ
18135        const D: u32 = 42;
18136
18137
18138        fn main() {
18139            println!("hello");
18140
18141            println!("world");
18142        }"#
18143        .unindent(),
18144    );
18145}
18146
18147#[gpui::test]
18148async fn test_stage_and_unstage_added_file_hunk(
18149    executor: BackgroundExecutor,
18150    cx: &mut TestAppContext,
18151) {
18152    init_test(cx, |_| {});
18153
18154    let mut cx = EditorTestContext::new(cx).await;
18155    cx.update_editor(|editor, _, cx| {
18156        editor.set_expand_all_diff_hunks(cx);
18157    });
18158
18159    let working_copy = r#"
18160            ˇfn main() {
18161                println!("hello, world!");
18162            }
18163        "#
18164    .unindent();
18165
18166    cx.set_state(&working_copy);
18167    executor.run_until_parked();
18168
18169    cx.assert_state_with_diff(
18170        r#"
18171            + ˇfn main() {
18172            +     println!("hello, world!");
18173            + }
18174        "#
18175        .unindent(),
18176    );
18177    cx.assert_index_text(None);
18178
18179    cx.update_editor(|editor, window, cx| {
18180        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18181    });
18182    executor.run_until_parked();
18183    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18184    cx.assert_state_with_diff(
18185        r#"
18186            + ˇfn main() {
18187            +     println!("hello, world!");
18188            + }
18189        "#
18190        .unindent(),
18191    );
18192
18193    cx.update_editor(|editor, window, cx| {
18194        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18195    });
18196    executor.run_until_parked();
18197    cx.assert_index_text(None);
18198}
18199
18200async fn setup_indent_guides_editor(
18201    text: &str,
18202    cx: &mut TestAppContext,
18203) -> (BufferId, EditorTestContext) {
18204    init_test(cx, |_| {});
18205
18206    let mut cx = EditorTestContext::new(cx).await;
18207
18208    let buffer_id = cx.update_editor(|editor, window, cx| {
18209        editor.set_text(text, window, cx);
18210        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18211
18212        buffer_ids[0]
18213    });
18214
18215    (buffer_id, cx)
18216}
18217
18218fn assert_indent_guides(
18219    range: Range<u32>,
18220    expected: Vec<IndentGuide>,
18221    active_indices: Option<Vec<usize>>,
18222    cx: &mut EditorTestContext,
18223) {
18224    let indent_guides = cx.update_editor(|editor, window, cx| {
18225        let snapshot = editor.snapshot(window, cx).display_snapshot;
18226        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18227            editor,
18228            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18229            true,
18230            &snapshot,
18231            cx,
18232        );
18233
18234        indent_guides.sort_by(|a, b| {
18235            a.depth.cmp(&b.depth).then(
18236                a.start_row
18237                    .cmp(&b.start_row)
18238                    .then(a.end_row.cmp(&b.end_row)),
18239            )
18240        });
18241        indent_guides
18242    });
18243
18244    if let Some(expected) = active_indices {
18245        let active_indices = cx.update_editor(|editor, window, cx| {
18246            let snapshot = editor.snapshot(window, cx).display_snapshot;
18247            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18248        });
18249
18250        assert_eq!(
18251            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18252            expected,
18253            "Active indent guide indices do not match"
18254        );
18255    }
18256
18257    assert_eq!(indent_guides, expected, "Indent guides do not match");
18258}
18259
18260fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18261    IndentGuide {
18262        buffer_id,
18263        start_row: MultiBufferRow(start_row),
18264        end_row: MultiBufferRow(end_row),
18265        depth,
18266        tab_size: 4,
18267        settings: IndentGuideSettings {
18268            enabled: true,
18269            line_width: 1,
18270            active_line_width: 1,
18271            ..Default::default()
18272        },
18273    }
18274}
18275
18276#[gpui::test]
18277async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18278    let (buffer_id, mut cx) = setup_indent_guides_editor(
18279        &"
18280        fn main() {
18281            let a = 1;
18282        }"
18283        .unindent(),
18284        cx,
18285    )
18286    .await;
18287
18288    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18289}
18290
18291#[gpui::test]
18292async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18293    let (buffer_id, mut cx) = setup_indent_guides_editor(
18294        &"
18295        fn main() {
18296            let a = 1;
18297            let b = 2;
18298        }"
18299        .unindent(),
18300        cx,
18301    )
18302    .await;
18303
18304    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18305}
18306
18307#[gpui::test]
18308async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18309    let (buffer_id, mut cx) = setup_indent_guides_editor(
18310        &"
18311        fn main() {
18312            let a = 1;
18313            if a == 3 {
18314                let b = 2;
18315            } else {
18316                let c = 3;
18317            }
18318        }"
18319        .unindent(),
18320        cx,
18321    )
18322    .await;
18323
18324    assert_indent_guides(
18325        0..8,
18326        vec![
18327            indent_guide(buffer_id, 1, 6, 0),
18328            indent_guide(buffer_id, 3, 3, 1),
18329            indent_guide(buffer_id, 5, 5, 1),
18330        ],
18331        None,
18332        &mut cx,
18333    );
18334}
18335
18336#[gpui::test]
18337async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18338    let (buffer_id, mut cx) = setup_indent_guides_editor(
18339        &"
18340        fn main() {
18341            let a = 1;
18342                let b = 2;
18343            let c = 3;
18344        }"
18345        .unindent(),
18346        cx,
18347    )
18348    .await;
18349
18350    assert_indent_guides(
18351        0..5,
18352        vec![
18353            indent_guide(buffer_id, 1, 3, 0),
18354            indent_guide(buffer_id, 2, 2, 1),
18355        ],
18356        None,
18357        &mut cx,
18358    );
18359}
18360
18361#[gpui::test]
18362async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18363    let (buffer_id, mut cx) = setup_indent_guides_editor(
18364        &"
18365        fn main() {
18366            let a = 1;
18367
18368            let c = 3;
18369        }"
18370        .unindent(),
18371        cx,
18372    )
18373    .await;
18374
18375    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18376}
18377
18378#[gpui::test]
18379async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18380    let (buffer_id, mut cx) = setup_indent_guides_editor(
18381        &"
18382        fn main() {
18383            let a = 1;
18384
18385            let c = 3;
18386
18387            if a == 3 {
18388                let b = 2;
18389            } else {
18390                let c = 3;
18391            }
18392        }"
18393        .unindent(),
18394        cx,
18395    )
18396    .await;
18397
18398    assert_indent_guides(
18399        0..11,
18400        vec![
18401            indent_guide(buffer_id, 1, 9, 0),
18402            indent_guide(buffer_id, 6, 6, 1),
18403            indent_guide(buffer_id, 8, 8, 1),
18404        ],
18405        None,
18406        &mut cx,
18407    );
18408}
18409
18410#[gpui::test]
18411async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18412    let (buffer_id, mut cx) = setup_indent_guides_editor(
18413        &"
18414        fn main() {
18415            let a = 1;
18416
18417            let c = 3;
18418
18419            if a == 3 {
18420                let b = 2;
18421            } else {
18422                let c = 3;
18423            }
18424        }"
18425        .unindent(),
18426        cx,
18427    )
18428    .await;
18429
18430    assert_indent_guides(
18431        1..11,
18432        vec![
18433            indent_guide(buffer_id, 1, 9, 0),
18434            indent_guide(buffer_id, 6, 6, 1),
18435            indent_guide(buffer_id, 8, 8, 1),
18436        ],
18437        None,
18438        &mut cx,
18439    );
18440}
18441
18442#[gpui::test]
18443async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18444    let (buffer_id, mut cx) = setup_indent_guides_editor(
18445        &"
18446        fn main() {
18447            let a = 1;
18448
18449            let c = 3;
18450
18451            if a == 3 {
18452                let b = 2;
18453            } else {
18454                let c = 3;
18455            }
18456        }"
18457        .unindent(),
18458        cx,
18459    )
18460    .await;
18461
18462    assert_indent_guides(
18463        1..10,
18464        vec![
18465            indent_guide(buffer_id, 1, 9, 0),
18466            indent_guide(buffer_id, 6, 6, 1),
18467            indent_guide(buffer_id, 8, 8, 1),
18468        ],
18469        None,
18470        &mut cx,
18471    );
18472}
18473
18474#[gpui::test]
18475async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18476    let (buffer_id, mut cx) = setup_indent_guides_editor(
18477        &"
18478        fn main() {
18479            if a {
18480                b(
18481                    c,
18482                    d,
18483                )
18484            } else {
18485                e(
18486                    f
18487                )
18488            }
18489        }"
18490        .unindent(),
18491        cx,
18492    )
18493    .await;
18494
18495    assert_indent_guides(
18496        0..11,
18497        vec![
18498            indent_guide(buffer_id, 1, 10, 0),
18499            indent_guide(buffer_id, 2, 5, 1),
18500            indent_guide(buffer_id, 7, 9, 1),
18501            indent_guide(buffer_id, 3, 4, 2),
18502            indent_guide(buffer_id, 8, 8, 2),
18503        ],
18504        None,
18505        &mut cx,
18506    );
18507
18508    cx.update_editor(|editor, window, cx| {
18509        editor.fold_at(MultiBufferRow(2), window, cx);
18510        assert_eq!(
18511            editor.display_text(cx),
18512            "
18513            fn main() {
18514                if a {
18515                    b(⋯
18516                    )
18517                } else {
18518                    e(
18519                        f
18520                    )
18521                }
18522            }"
18523            .unindent()
18524        );
18525    });
18526
18527    assert_indent_guides(
18528        0..11,
18529        vec![
18530            indent_guide(buffer_id, 1, 10, 0),
18531            indent_guide(buffer_id, 2, 5, 1),
18532            indent_guide(buffer_id, 7, 9, 1),
18533            indent_guide(buffer_id, 8, 8, 2),
18534        ],
18535        None,
18536        &mut cx,
18537    );
18538}
18539
18540#[gpui::test]
18541async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18542    let (buffer_id, mut cx) = setup_indent_guides_editor(
18543        &"
18544        block1
18545            block2
18546                block3
18547                    block4
18548            block2
18549        block1
18550        block1"
18551            .unindent(),
18552        cx,
18553    )
18554    .await;
18555
18556    assert_indent_guides(
18557        1..10,
18558        vec![
18559            indent_guide(buffer_id, 1, 4, 0),
18560            indent_guide(buffer_id, 2, 3, 1),
18561            indent_guide(buffer_id, 3, 3, 2),
18562        ],
18563        None,
18564        &mut cx,
18565    );
18566}
18567
18568#[gpui::test]
18569async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18570    let (buffer_id, mut cx) = setup_indent_guides_editor(
18571        &"
18572        block1
18573            block2
18574                block3
18575
18576        block1
18577        block1"
18578            .unindent(),
18579        cx,
18580    )
18581    .await;
18582
18583    assert_indent_guides(
18584        0..6,
18585        vec![
18586            indent_guide(buffer_id, 1, 2, 0),
18587            indent_guide(buffer_id, 2, 2, 1),
18588        ],
18589        None,
18590        &mut cx,
18591    );
18592}
18593
18594#[gpui::test]
18595async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18596    let (buffer_id, mut cx) = setup_indent_guides_editor(
18597        &"
18598        function component() {
18599        \treturn (
18600        \t\t\t
18601        \t\t<div>
18602        \t\t\t<abc></abc>
18603        \t\t</div>
18604        \t)
18605        }"
18606        .unindent(),
18607        cx,
18608    )
18609    .await;
18610
18611    assert_indent_guides(
18612        0..8,
18613        vec![
18614            indent_guide(buffer_id, 1, 6, 0),
18615            indent_guide(buffer_id, 2, 5, 1),
18616            indent_guide(buffer_id, 4, 4, 2),
18617        ],
18618        None,
18619        &mut cx,
18620    );
18621}
18622
18623#[gpui::test]
18624async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18625    let (buffer_id, mut cx) = setup_indent_guides_editor(
18626        &"
18627        function component() {
18628        \treturn (
18629        \t
18630        \t\t<div>
18631        \t\t\t<abc></abc>
18632        \t\t</div>
18633        \t)
18634        }"
18635        .unindent(),
18636        cx,
18637    )
18638    .await;
18639
18640    assert_indent_guides(
18641        0..8,
18642        vec![
18643            indent_guide(buffer_id, 1, 6, 0),
18644            indent_guide(buffer_id, 2, 5, 1),
18645            indent_guide(buffer_id, 4, 4, 2),
18646        ],
18647        None,
18648        &mut cx,
18649    );
18650}
18651
18652#[gpui::test]
18653async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18654    let (buffer_id, mut cx) = setup_indent_guides_editor(
18655        &"
18656        block1
18657
18658
18659
18660            block2
18661        "
18662        .unindent(),
18663        cx,
18664    )
18665    .await;
18666
18667    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18668}
18669
18670#[gpui::test]
18671async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18672    let (buffer_id, mut cx) = setup_indent_guides_editor(
18673        &"
18674        def a:
18675        \tb = 3
18676        \tif True:
18677        \t\tc = 4
18678        \t\td = 5
18679        \tprint(b)
18680        "
18681        .unindent(),
18682        cx,
18683    )
18684    .await;
18685
18686    assert_indent_guides(
18687        0..6,
18688        vec![
18689            indent_guide(buffer_id, 1, 5, 0),
18690            indent_guide(buffer_id, 3, 4, 1),
18691        ],
18692        None,
18693        &mut cx,
18694    );
18695}
18696
18697#[gpui::test]
18698async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18699    let (buffer_id, mut cx) = setup_indent_guides_editor(
18700        &"
18701    fn main() {
18702        let a = 1;
18703    }"
18704        .unindent(),
18705        cx,
18706    )
18707    .await;
18708
18709    cx.update_editor(|editor, window, cx| {
18710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18711            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18712        });
18713    });
18714
18715    assert_indent_guides(
18716        0..3,
18717        vec![indent_guide(buffer_id, 1, 1, 0)],
18718        Some(vec![0]),
18719        &mut cx,
18720    );
18721}
18722
18723#[gpui::test]
18724async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18725    let (buffer_id, mut cx) = setup_indent_guides_editor(
18726        &"
18727    fn main() {
18728        if 1 == 2 {
18729            let a = 1;
18730        }
18731    }"
18732        .unindent(),
18733        cx,
18734    )
18735    .await;
18736
18737    cx.update_editor(|editor, window, cx| {
18738        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18739            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18740        });
18741    });
18742
18743    assert_indent_guides(
18744        0..4,
18745        vec![
18746            indent_guide(buffer_id, 1, 3, 0),
18747            indent_guide(buffer_id, 2, 2, 1),
18748        ],
18749        Some(vec![1]),
18750        &mut cx,
18751    );
18752
18753    cx.update_editor(|editor, window, cx| {
18754        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18755            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18756        });
18757    });
18758
18759    assert_indent_guides(
18760        0..4,
18761        vec![
18762            indent_guide(buffer_id, 1, 3, 0),
18763            indent_guide(buffer_id, 2, 2, 1),
18764        ],
18765        Some(vec![1]),
18766        &mut cx,
18767    );
18768
18769    cx.update_editor(|editor, window, cx| {
18770        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18771            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18772        });
18773    });
18774
18775    assert_indent_guides(
18776        0..4,
18777        vec![
18778            indent_guide(buffer_id, 1, 3, 0),
18779            indent_guide(buffer_id, 2, 2, 1),
18780        ],
18781        Some(vec![0]),
18782        &mut cx,
18783    );
18784}
18785
18786#[gpui::test]
18787async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18788    let (buffer_id, mut cx) = setup_indent_guides_editor(
18789        &"
18790    fn main() {
18791        let a = 1;
18792
18793        let b = 2;
18794    }"
18795        .unindent(),
18796        cx,
18797    )
18798    .await;
18799
18800    cx.update_editor(|editor, window, cx| {
18801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18802            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18803        });
18804    });
18805
18806    assert_indent_guides(
18807        0..5,
18808        vec![indent_guide(buffer_id, 1, 3, 0)],
18809        Some(vec![0]),
18810        &mut cx,
18811    );
18812}
18813
18814#[gpui::test]
18815async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18816    let (buffer_id, mut cx) = setup_indent_guides_editor(
18817        &"
18818    def m:
18819        a = 1
18820        pass"
18821            .unindent(),
18822        cx,
18823    )
18824    .await;
18825
18826    cx.update_editor(|editor, window, cx| {
18827        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18828            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18829        });
18830    });
18831
18832    assert_indent_guides(
18833        0..3,
18834        vec![indent_guide(buffer_id, 1, 2, 0)],
18835        Some(vec![0]),
18836        &mut cx,
18837    );
18838}
18839
18840#[gpui::test]
18841async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18842    init_test(cx, |_| {});
18843    let mut cx = EditorTestContext::new(cx).await;
18844    let text = indoc! {
18845        "
18846        impl A {
18847            fn b() {
18848                0;
18849                3;
18850                5;
18851                6;
18852                7;
18853            }
18854        }
18855        "
18856    };
18857    let base_text = indoc! {
18858        "
18859        impl A {
18860            fn b() {
18861                0;
18862                1;
18863                2;
18864                3;
18865                4;
18866            }
18867            fn c() {
18868                5;
18869                6;
18870                7;
18871            }
18872        }
18873        "
18874    };
18875
18876    cx.update_editor(|editor, window, cx| {
18877        editor.set_text(text, window, cx);
18878
18879        editor.buffer().update(cx, |multibuffer, cx| {
18880            let buffer = multibuffer.as_singleton().unwrap();
18881            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18882
18883            multibuffer.set_all_diff_hunks_expanded(cx);
18884            multibuffer.add_diff(diff, cx);
18885
18886            buffer.read(cx).remote_id()
18887        })
18888    });
18889    cx.run_until_parked();
18890
18891    cx.assert_state_with_diff(
18892        indoc! { "
18893          impl A {
18894              fn b() {
18895                  0;
18896        -         1;
18897        -         2;
18898                  3;
18899        -         4;
18900        -     }
18901        -     fn c() {
18902                  5;
18903                  6;
18904                  7;
18905              }
18906          }
18907          ˇ"
18908        }
18909        .to_string(),
18910    );
18911
18912    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18913        editor
18914            .snapshot(window, cx)
18915            .buffer_snapshot
18916            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18917            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18918            .collect::<Vec<_>>()
18919    });
18920    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18921    assert_eq!(
18922        actual_guides,
18923        vec![
18924            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18925            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18926            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18927        ]
18928    );
18929}
18930
18931#[gpui::test]
18932async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18933    init_test(cx, |_| {});
18934    let mut cx = EditorTestContext::new(cx).await;
18935
18936    let diff_base = r#"
18937        a
18938        b
18939        c
18940        "#
18941    .unindent();
18942
18943    cx.set_state(
18944        &r#"
18945        ˇA
18946        b
18947        C
18948        "#
18949        .unindent(),
18950    );
18951    cx.set_head_text(&diff_base);
18952    cx.update_editor(|editor, window, cx| {
18953        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18954    });
18955    executor.run_until_parked();
18956
18957    let both_hunks_expanded = r#"
18958        - a
18959        + ˇA
18960          b
18961        - c
18962        + C
18963        "#
18964    .unindent();
18965
18966    cx.assert_state_with_diff(both_hunks_expanded.clone());
18967
18968    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18969        let snapshot = editor.snapshot(window, cx);
18970        let hunks = editor
18971            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18972            .collect::<Vec<_>>();
18973        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18974        let buffer_id = hunks[0].buffer_id;
18975        hunks
18976            .into_iter()
18977            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18978            .collect::<Vec<_>>()
18979    });
18980    assert_eq!(hunk_ranges.len(), 2);
18981
18982    cx.update_editor(|editor, _, cx| {
18983        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18984    });
18985    executor.run_until_parked();
18986
18987    let second_hunk_expanded = r#"
18988          ˇA
18989          b
18990        - c
18991        + C
18992        "#
18993    .unindent();
18994
18995    cx.assert_state_with_diff(second_hunk_expanded);
18996
18997    cx.update_editor(|editor, _, cx| {
18998        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18999    });
19000    executor.run_until_parked();
19001
19002    cx.assert_state_with_diff(both_hunks_expanded.clone());
19003
19004    cx.update_editor(|editor, _, cx| {
19005        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19006    });
19007    executor.run_until_parked();
19008
19009    let first_hunk_expanded = r#"
19010        - a
19011        + ˇA
19012          b
19013          C
19014        "#
19015    .unindent();
19016
19017    cx.assert_state_with_diff(first_hunk_expanded);
19018
19019    cx.update_editor(|editor, _, cx| {
19020        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19021    });
19022    executor.run_until_parked();
19023
19024    cx.assert_state_with_diff(both_hunks_expanded);
19025
19026    cx.set_state(
19027        &r#"
19028        ˇA
19029        b
19030        "#
19031        .unindent(),
19032    );
19033    cx.run_until_parked();
19034
19035    // TODO this cursor position seems bad
19036    cx.assert_state_with_diff(
19037        r#"
19038        - ˇa
19039        + A
19040          b
19041        "#
19042        .unindent(),
19043    );
19044
19045    cx.update_editor(|editor, window, cx| {
19046        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19047    });
19048
19049    cx.assert_state_with_diff(
19050        r#"
19051            - ˇa
19052            + A
19053              b
19054            - c
19055            "#
19056        .unindent(),
19057    );
19058
19059    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19060        let snapshot = editor.snapshot(window, cx);
19061        let hunks = editor
19062            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19063            .collect::<Vec<_>>();
19064        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19065        let buffer_id = hunks[0].buffer_id;
19066        hunks
19067            .into_iter()
19068            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19069            .collect::<Vec<_>>()
19070    });
19071    assert_eq!(hunk_ranges.len(), 2);
19072
19073    cx.update_editor(|editor, _, cx| {
19074        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19075    });
19076    executor.run_until_parked();
19077
19078    cx.assert_state_with_diff(
19079        r#"
19080        - ˇa
19081        + A
19082          b
19083        "#
19084        .unindent(),
19085    );
19086}
19087
19088#[gpui::test]
19089async fn test_toggle_deletion_hunk_at_start_of_file(
19090    executor: BackgroundExecutor,
19091    cx: &mut TestAppContext,
19092) {
19093    init_test(cx, |_| {});
19094    let mut cx = EditorTestContext::new(cx).await;
19095
19096    let diff_base = r#"
19097        a
19098        b
19099        c
19100        "#
19101    .unindent();
19102
19103    cx.set_state(
19104        &r#"
19105        ˇb
19106        c
19107        "#
19108        .unindent(),
19109    );
19110    cx.set_head_text(&diff_base);
19111    cx.update_editor(|editor, window, cx| {
19112        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19113    });
19114    executor.run_until_parked();
19115
19116    let hunk_expanded = r#"
19117        - a
19118          ˇb
19119          c
19120        "#
19121    .unindent();
19122
19123    cx.assert_state_with_diff(hunk_expanded.clone());
19124
19125    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19126        let snapshot = editor.snapshot(window, cx);
19127        let hunks = editor
19128            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19129            .collect::<Vec<_>>();
19130        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19131        let buffer_id = hunks[0].buffer_id;
19132        hunks
19133            .into_iter()
19134            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19135            .collect::<Vec<_>>()
19136    });
19137    assert_eq!(hunk_ranges.len(), 1);
19138
19139    cx.update_editor(|editor, _, cx| {
19140        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19141    });
19142    executor.run_until_parked();
19143
19144    let hunk_collapsed = r#"
19145          ˇb
19146          c
19147        "#
19148    .unindent();
19149
19150    cx.assert_state_with_diff(hunk_collapsed);
19151
19152    cx.update_editor(|editor, _, cx| {
19153        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19154    });
19155    executor.run_until_parked();
19156
19157    cx.assert_state_with_diff(hunk_expanded.clone());
19158}
19159
19160#[gpui::test]
19161async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19162    init_test(cx, |_| {});
19163
19164    let fs = FakeFs::new(cx.executor());
19165    fs.insert_tree(
19166        path!("/test"),
19167        json!({
19168            ".git": {},
19169            "file-1": "ONE\n",
19170            "file-2": "TWO\n",
19171            "file-3": "THREE\n",
19172        }),
19173    )
19174    .await;
19175
19176    fs.set_head_for_repo(
19177        path!("/test/.git").as_ref(),
19178        &[
19179            ("file-1".into(), "one\n".into()),
19180            ("file-2".into(), "two\n".into()),
19181            ("file-3".into(), "three\n".into()),
19182        ],
19183        "deadbeef",
19184    );
19185
19186    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19187    let mut buffers = vec![];
19188    for i in 1..=3 {
19189        let buffer = project
19190            .update(cx, |project, cx| {
19191                let path = format!(path!("/test/file-{}"), i);
19192                project.open_local_buffer(path, cx)
19193            })
19194            .await
19195            .unwrap();
19196        buffers.push(buffer);
19197    }
19198
19199    let multibuffer = cx.new(|cx| {
19200        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19201        multibuffer.set_all_diff_hunks_expanded(cx);
19202        for buffer in &buffers {
19203            let snapshot = buffer.read(cx).snapshot();
19204            multibuffer.set_excerpts_for_path(
19205                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19206                buffer.clone(),
19207                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19208                DEFAULT_MULTIBUFFER_CONTEXT,
19209                cx,
19210            );
19211        }
19212        multibuffer
19213    });
19214
19215    let editor = cx.add_window(|window, cx| {
19216        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19217    });
19218    cx.run_until_parked();
19219
19220    let snapshot = editor
19221        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19222        .unwrap();
19223    let hunks = snapshot
19224        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19225        .map(|hunk| match hunk {
19226            DisplayDiffHunk::Unfolded {
19227                display_row_range, ..
19228            } => display_row_range,
19229            DisplayDiffHunk::Folded { .. } => unreachable!(),
19230        })
19231        .collect::<Vec<_>>();
19232    assert_eq!(
19233        hunks,
19234        [
19235            DisplayRow(2)..DisplayRow(4),
19236            DisplayRow(7)..DisplayRow(9),
19237            DisplayRow(12)..DisplayRow(14),
19238        ]
19239    );
19240}
19241
19242#[gpui::test]
19243async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19244    init_test(cx, |_| {});
19245
19246    let mut cx = EditorTestContext::new(cx).await;
19247    cx.set_head_text(indoc! { "
19248        one
19249        two
19250        three
19251        four
19252        five
19253        "
19254    });
19255    cx.set_index_text(indoc! { "
19256        one
19257        two
19258        three
19259        four
19260        five
19261        "
19262    });
19263    cx.set_state(indoc! {"
19264        one
19265        TWO
19266        ˇTHREE
19267        FOUR
19268        five
19269    "});
19270    cx.run_until_parked();
19271    cx.update_editor(|editor, window, cx| {
19272        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19273    });
19274    cx.run_until_parked();
19275    cx.assert_index_text(Some(indoc! {"
19276        one
19277        TWO
19278        THREE
19279        FOUR
19280        five
19281    "}));
19282    cx.set_state(indoc! { "
19283        one
19284        TWO
19285        ˇTHREE-HUNDRED
19286        FOUR
19287        five
19288    "});
19289    cx.run_until_parked();
19290    cx.update_editor(|editor, window, cx| {
19291        let snapshot = editor.snapshot(window, cx);
19292        let hunks = editor
19293            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19294            .collect::<Vec<_>>();
19295        assert_eq!(hunks.len(), 1);
19296        assert_eq!(
19297            hunks[0].status(),
19298            DiffHunkStatus {
19299                kind: DiffHunkStatusKind::Modified,
19300                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19301            }
19302        );
19303
19304        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19305    });
19306    cx.run_until_parked();
19307    cx.assert_index_text(Some(indoc! {"
19308        one
19309        TWO
19310        THREE-HUNDRED
19311        FOUR
19312        five
19313    "}));
19314}
19315
19316#[gpui::test]
19317fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19318    init_test(cx, |_| {});
19319
19320    let editor = cx.add_window(|window, cx| {
19321        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19322        build_editor(buffer, window, cx)
19323    });
19324
19325    let render_args = Arc::new(Mutex::new(None));
19326    let snapshot = editor
19327        .update(cx, |editor, window, cx| {
19328            let snapshot = editor.buffer().read(cx).snapshot(cx);
19329            let range =
19330                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19331
19332            struct RenderArgs {
19333                row: MultiBufferRow,
19334                folded: bool,
19335                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19336            }
19337
19338            let crease = Crease::inline(
19339                range,
19340                FoldPlaceholder::test(),
19341                {
19342                    let toggle_callback = render_args.clone();
19343                    move |row, folded, callback, _window, _cx| {
19344                        *toggle_callback.lock() = Some(RenderArgs {
19345                            row,
19346                            folded,
19347                            callback,
19348                        });
19349                        div()
19350                    }
19351                },
19352                |_row, _folded, _window, _cx| div(),
19353            );
19354
19355            editor.insert_creases(Some(crease), cx);
19356            let snapshot = editor.snapshot(window, cx);
19357            let _div = snapshot.render_crease_toggle(
19358                MultiBufferRow(1),
19359                false,
19360                cx.entity().clone(),
19361                window,
19362                cx,
19363            );
19364            snapshot
19365        })
19366        .unwrap();
19367
19368    let render_args = render_args.lock().take().unwrap();
19369    assert_eq!(render_args.row, MultiBufferRow(1));
19370    assert!(!render_args.folded);
19371    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19372
19373    cx.update_window(*editor, |_, window, cx| {
19374        (render_args.callback)(true, window, cx)
19375    })
19376    .unwrap();
19377    let snapshot = editor
19378        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19379        .unwrap();
19380    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19381
19382    cx.update_window(*editor, |_, window, cx| {
19383        (render_args.callback)(false, window, cx)
19384    })
19385    .unwrap();
19386    let snapshot = editor
19387        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19388        .unwrap();
19389    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19390}
19391
19392#[gpui::test]
19393async fn test_input_text(cx: &mut TestAppContext) {
19394    init_test(cx, |_| {});
19395    let mut cx = EditorTestContext::new(cx).await;
19396
19397    cx.set_state(
19398        &r#"ˇone
19399        two
19400
19401        three
19402        fourˇ
19403        five
19404
19405        siˇx"#
19406            .unindent(),
19407    );
19408
19409    cx.dispatch_action(HandleInput(String::new()));
19410    cx.assert_editor_state(
19411        &r#"ˇone
19412        two
19413
19414        three
19415        fourˇ
19416        five
19417
19418        siˇx"#
19419            .unindent(),
19420    );
19421
19422    cx.dispatch_action(HandleInput("AAAA".to_string()));
19423    cx.assert_editor_state(
19424        &r#"AAAAˇone
19425        two
19426
19427        three
19428        fourAAAAˇ
19429        five
19430
19431        siAAAAˇx"#
19432            .unindent(),
19433    );
19434}
19435
19436#[gpui::test]
19437async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19438    init_test(cx, |_| {});
19439
19440    let mut cx = EditorTestContext::new(cx).await;
19441    cx.set_state(
19442        r#"let foo = 1;
19443let foo = 2;
19444let foo = 3;
19445let fooˇ = 4;
19446let foo = 5;
19447let foo = 6;
19448let foo = 7;
19449let foo = 8;
19450let foo = 9;
19451let foo = 10;
19452let foo = 11;
19453let foo = 12;
19454let foo = 13;
19455let foo = 14;
19456let foo = 15;"#,
19457    );
19458
19459    cx.update_editor(|e, window, cx| {
19460        assert_eq!(
19461            e.next_scroll_position,
19462            NextScrollCursorCenterTopBottom::Center,
19463            "Default next scroll direction is center",
19464        );
19465
19466        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19467        assert_eq!(
19468            e.next_scroll_position,
19469            NextScrollCursorCenterTopBottom::Top,
19470            "After center, next scroll direction should be top",
19471        );
19472
19473        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19474        assert_eq!(
19475            e.next_scroll_position,
19476            NextScrollCursorCenterTopBottom::Bottom,
19477            "After top, next scroll direction should be bottom",
19478        );
19479
19480        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19481        assert_eq!(
19482            e.next_scroll_position,
19483            NextScrollCursorCenterTopBottom::Center,
19484            "After bottom, scrolling should start over",
19485        );
19486
19487        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19488        assert_eq!(
19489            e.next_scroll_position,
19490            NextScrollCursorCenterTopBottom::Top,
19491            "Scrolling continues if retriggered fast enough"
19492        );
19493    });
19494
19495    cx.executor()
19496        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19497    cx.executor().run_until_parked();
19498    cx.update_editor(|e, _, _| {
19499        assert_eq!(
19500            e.next_scroll_position,
19501            NextScrollCursorCenterTopBottom::Center,
19502            "If scrolling is not triggered fast enough, it should reset"
19503        );
19504    });
19505}
19506
19507#[gpui::test]
19508async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19509    init_test(cx, |_| {});
19510    let mut cx = EditorLspTestContext::new_rust(
19511        lsp::ServerCapabilities {
19512            definition_provider: Some(lsp::OneOf::Left(true)),
19513            references_provider: Some(lsp::OneOf::Left(true)),
19514            ..lsp::ServerCapabilities::default()
19515        },
19516        cx,
19517    )
19518    .await;
19519
19520    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19521        let go_to_definition = cx
19522            .lsp
19523            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19524                move |params, _| async move {
19525                    if empty_go_to_definition {
19526                        Ok(None)
19527                    } else {
19528                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19529                            uri: params.text_document_position_params.text_document.uri,
19530                            range: lsp::Range::new(
19531                                lsp::Position::new(4, 3),
19532                                lsp::Position::new(4, 6),
19533                            ),
19534                        })))
19535                    }
19536                },
19537            );
19538        let references = cx
19539            .lsp
19540            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19541                Ok(Some(vec![lsp::Location {
19542                    uri: params.text_document_position.text_document.uri,
19543                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19544                }]))
19545            });
19546        (go_to_definition, references)
19547    };
19548
19549    cx.set_state(
19550        &r#"fn one() {
19551            let mut a = ˇtwo();
19552        }
19553
19554        fn two() {}"#
19555            .unindent(),
19556    );
19557    set_up_lsp_handlers(false, &mut cx);
19558    let navigated = cx
19559        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19560        .await
19561        .expect("Failed to navigate to definition");
19562    assert_eq!(
19563        navigated,
19564        Navigated::Yes,
19565        "Should have navigated to definition from the GetDefinition response"
19566    );
19567    cx.assert_editor_state(
19568        &r#"fn one() {
19569            let mut a = two();
19570        }
19571
19572        fn «twoˇ»() {}"#
19573            .unindent(),
19574    );
19575
19576    let editors = cx.update_workspace(|workspace, _, cx| {
19577        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19578    });
19579    cx.update_editor(|_, _, test_editor_cx| {
19580        assert_eq!(
19581            editors.len(),
19582            1,
19583            "Initially, only one, test, editor should be open in the workspace"
19584        );
19585        assert_eq!(
19586            test_editor_cx.entity(),
19587            editors.last().expect("Asserted len is 1").clone()
19588        );
19589    });
19590
19591    set_up_lsp_handlers(true, &mut cx);
19592    let navigated = cx
19593        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19594        .await
19595        .expect("Failed to navigate to lookup references");
19596    assert_eq!(
19597        navigated,
19598        Navigated::Yes,
19599        "Should have navigated to references as a fallback after empty GoToDefinition response"
19600    );
19601    // We should not change the selections in the existing file,
19602    // if opening another milti buffer with the references
19603    cx.assert_editor_state(
19604        &r#"fn one() {
19605            let mut a = two();
19606        }
19607
19608        fn «twoˇ»() {}"#
19609            .unindent(),
19610    );
19611    let editors = cx.update_workspace(|workspace, _, cx| {
19612        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19613    });
19614    cx.update_editor(|_, _, test_editor_cx| {
19615        assert_eq!(
19616            editors.len(),
19617            2,
19618            "After falling back to references search, we open a new editor with the results"
19619        );
19620        let references_fallback_text = editors
19621            .into_iter()
19622            .find(|new_editor| *new_editor != test_editor_cx.entity())
19623            .expect("Should have one non-test editor now")
19624            .read(test_editor_cx)
19625            .text(test_editor_cx);
19626        assert_eq!(
19627            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19628            "Should use the range from the references response and not the GoToDefinition one"
19629        );
19630    });
19631}
19632
19633#[gpui::test]
19634async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19635    init_test(cx, |_| {});
19636    cx.update(|cx| {
19637        let mut editor_settings = EditorSettings::get_global(cx).clone();
19638        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19639        EditorSettings::override_global(editor_settings, cx);
19640    });
19641    let mut cx = EditorLspTestContext::new_rust(
19642        lsp::ServerCapabilities {
19643            definition_provider: Some(lsp::OneOf::Left(true)),
19644            references_provider: Some(lsp::OneOf::Left(true)),
19645            ..lsp::ServerCapabilities::default()
19646        },
19647        cx,
19648    )
19649    .await;
19650    let original_state = r#"fn one() {
19651        let mut a = ˇtwo();
19652    }
19653
19654    fn two() {}"#
19655        .unindent();
19656    cx.set_state(&original_state);
19657
19658    let mut go_to_definition = cx
19659        .lsp
19660        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19661            move |_, _| async move { Ok(None) },
19662        );
19663    let _references = cx
19664        .lsp
19665        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19666            panic!("Should not call for references with no go to definition fallback")
19667        });
19668
19669    let navigated = cx
19670        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19671        .await
19672        .expect("Failed to navigate to lookup references");
19673    go_to_definition
19674        .next()
19675        .await
19676        .expect("Should have called the go_to_definition handler");
19677
19678    assert_eq!(
19679        navigated,
19680        Navigated::No,
19681        "Should have navigated to references as a fallback after empty GoToDefinition response"
19682    );
19683    cx.assert_editor_state(&original_state);
19684    let editors = cx.update_workspace(|workspace, _, cx| {
19685        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19686    });
19687    cx.update_editor(|_, _, _| {
19688        assert_eq!(
19689            editors.len(),
19690            1,
19691            "After unsuccessful fallback, no other editor should have been opened"
19692        );
19693    });
19694}
19695
19696#[gpui::test]
19697async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19698    init_test(cx, |_| {});
19699
19700    let language = Arc::new(Language::new(
19701        LanguageConfig::default(),
19702        Some(tree_sitter_rust::LANGUAGE.into()),
19703    ));
19704
19705    let text = r#"
19706        #[cfg(test)]
19707        mod tests() {
19708            #[test]
19709            fn runnable_1() {
19710                let a = 1;
19711            }
19712
19713            #[test]
19714            fn runnable_2() {
19715                let a = 1;
19716                let b = 2;
19717            }
19718        }
19719    "#
19720    .unindent();
19721
19722    let fs = FakeFs::new(cx.executor());
19723    fs.insert_file("/file.rs", Default::default()).await;
19724
19725    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19726    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19727    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19728    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19729    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19730
19731    let editor = cx.new_window_entity(|window, cx| {
19732        Editor::new(
19733            EditorMode::full(),
19734            multi_buffer,
19735            Some(project.clone()),
19736            window,
19737            cx,
19738        )
19739    });
19740
19741    editor.update_in(cx, |editor, window, cx| {
19742        let snapshot = editor.buffer().read(cx).snapshot(cx);
19743        editor.tasks.insert(
19744            (buffer.read(cx).remote_id(), 3),
19745            RunnableTasks {
19746                templates: vec![],
19747                offset: snapshot.anchor_before(43),
19748                column: 0,
19749                extra_variables: HashMap::default(),
19750                context_range: BufferOffset(43)..BufferOffset(85),
19751            },
19752        );
19753        editor.tasks.insert(
19754            (buffer.read(cx).remote_id(), 8),
19755            RunnableTasks {
19756                templates: vec![],
19757                offset: snapshot.anchor_before(86),
19758                column: 0,
19759                extra_variables: HashMap::default(),
19760                context_range: BufferOffset(86)..BufferOffset(191),
19761            },
19762        );
19763
19764        // Test finding task when cursor is inside function body
19765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19766            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19767        });
19768        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19769        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19770
19771        // Test finding task when cursor is on function name
19772        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19773            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19774        });
19775        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19776        assert_eq!(row, 8, "Should find task when cursor is on function name");
19777    });
19778}
19779
19780#[gpui::test]
19781async fn test_folding_buffers(cx: &mut TestAppContext) {
19782    init_test(cx, |_| {});
19783
19784    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19785    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19786    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19787
19788    let fs = FakeFs::new(cx.executor());
19789    fs.insert_tree(
19790        path!("/a"),
19791        json!({
19792            "first.rs": sample_text_1,
19793            "second.rs": sample_text_2,
19794            "third.rs": sample_text_3,
19795        }),
19796    )
19797    .await;
19798    let project = Project::test(fs, [path!("/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 worktree = project.update(cx, |project, cx| {
19802        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19803        assert_eq!(worktrees.len(), 1);
19804        worktrees.pop().unwrap()
19805    });
19806    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19807
19808    let buffer_1 = project
19809        .update(cx, |project, cx| {
19810            project.open_buffer((worktree_id, "first.rs"), cx)
19811        })
19812        .await
19813        .unwrap();
19814    let buffer_2 = project
19815        .update(cx, |project, cx| {
19816            project.open_buffer((worktree_id, "second.rs"), cx)
19817        })
19818        .await
19819        .unwrap();
19820    let buffer_3 = project
19821        .update(cx, |project, cx| {
19822            project.open_buffer((worktree_id, "third.rs"), cx)
19823        })
19824        .await
19825        .unwrap();
19826
19827    let multi_buffer = cx.new(|cx| {
19828        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19829        multi_buffer.push_excerpts(
19830            buffer_1.clone(),
19831            [
19832                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19833                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19834                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19835            ],
19836            cx,
19837        );
19838        multi_buffer.push_excerpts(
19839            buffer_2.clone(),
19840            [
19841                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19842                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19843                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19844            ],
19845            cx,
19846        );
19847        multi_buffer.push_excerpts(
19848            buffer_3.clone(),
19849            [
19850                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19851                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19852                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19853            ],
19854            cx,
19855        );
19856        multi_buffer
19857    });
19858    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19859        Editor::new(
19860            EditorMode::full(),
19861            multi_buffer.clone(),
19862            Some(project.clone()),
19863            window,
19864            cx,
19865        )
19866    });
19867
19868    assert_eq!(
19869        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19870        "\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",
19871    );
19872
19873    multi_buffer_editor.update(cx, |editor, cx| {
19874        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19875    });
19876    assert_eq!(
19877        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19878        "\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",
19879        "After folding the first buffer, its text should not be displayed"
19880    );
19881
19882    multi_buffer_editor.update(cx, |editor, cx| {
19883        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19884    });
19885    assert_eq!(
19886        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19887        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19888        "After folding the second buffer, its text should not be displayed"
19889    );
19890
19891    multi_buffer_editor.update(cx, |editor, cx| {
19892        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19893    });
19894    assert_eq!(
19895        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19896        "\n\n\n\n\n",
19897        "After folding the third buffer, its text should not be displayed"
19898    );
19899
19900    // Emulate selection inside the fold logic, that should work
19901    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19902        editor
19903            .snapshot(window, cx)
19904            .next_line_boundary(Point::new(0, 4));
19905    });
19906
19907    multi_buffer_editor.update(cx, |editor, cx| {
19908        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19909    });
19910    assert_eq!(
19911        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19912        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19913        "After unfolding the second buffer, its text should be displayed"
19914    );
19915
19916    // Typing inside of buffer 1 causes that buffer to be unfolded.
19917    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19918        assert_eq!(
19919            multi_buffer
19920                .read(cx)
19921                .snapshot(cx)
19922                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19923                .collect::<String>(),
19924            "bbbb"
19925        );
19926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19927            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19928        });
19929        editor.handle_input("B", window, cx);
19930    });
19931
19932    assert_eq!(
19933        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19934        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19935        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19936    );
19937
19938    multi_buffer_editor.update(cx, |editor, cx| {
19939        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19940    });
19941    assert_eq!(
19942        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19943        "\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",
19944        "After unfolding the all buffers, all original text should be displayed"
19945    );
19946}
19947
19948#[gpui::test]
19949async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19950    init_test(cx, |_| {});
19951
19952    let sample_text_1 = "1111\n2222\n3333".to_string();
19953    let sample_text_2 = "4444\n5555\n6666".to_string();
19954    let sample_text_3 = "7777\n8888\n9999".to_string();
19955
19956    let fs = FakeFs::new(cx.executor());
19957    fs.insert_tree(
19958        path!("/a"),
19959        json!({
19960            "first.rs": sample_text_1,
19961            "second.rs": sample_text_2,
19962            "third.rs": sample_text_3,
19963        }),
19964    )
19965    .await;
19966    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19967    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19968    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19969    let worktree = project.update(cx, |project, cx| {
19970        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19971        assert_eq!(worktrees.len(), 1);
19972        worktrees.pop().unwrap()
19973    });
19974    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19975
19976    let buffer_1 = project
19977        .update(cx, |project, cx| {
19978            project.open_buffer((worktree_id, "first.rs"), cx)
19979        })
19980        .await
19981        .unwrap();
19982    let buffer_2 = project
19983        .update(cx, |project, cx| {
19984            project.open_buffer((worktree_id, "second.rs"), cx)
19985        })
19986        .await
19987        .unwrap();
19988    let buffer_3 = project
19989        .update(cx, |project, cx| {
19990            project.open_buffer((worktree_id, "third.rs"), cx)
19991        })
19992        .await
19993        .unwrap();
19994
19995    let multi_buffer = cx.new(|cx| {
19996        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19997        multi_buffer.push_excerpts(
19998            buffer_1.clone(),
19999            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20000            cx,
20001        );
20002        multi_buffer.push_excerpts(
20003            buffer_2.clone(),
20004            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20005            cx,
20006        );
20007        multi_buffer.push_excerpts(
20008            buffer_3.clone(),
20009            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20010            cx,
20011        );
20012        multi_buffer
20013    });
20014
20015    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20016        Editor::new(
20017            EditorMode::full(),
20018            multi_buffer,
20019            Some(project.clone()),
20020            window,
20021            cx,
20022        )
20023    });
20024
20025    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20026    assert_eq!(
20027        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20028        full_text,
20029    );
20030
20031    multi_buffer_editor.update(cx, |editor, cx| {
20032        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20033    });
20034    assert_eq!(
20035        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20036        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20037        "After folding the first buffer, its text should not be displayed"
20038    );
20039
20040    multi_buffer_editor.update(cx, |editor, cx| {
20041        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20042    });
20043
20044    assert_eq!(
20045        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20046        "\n\n\n\n\n\n7777\n8888\n9999",
20047        "After folding the second buffer, its text should not be displayed"
20048    );
20049
20050    multi_buffer_editor.update(cx, |editor, cx| {
20051        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20052    });
20053    assert_eq!(
20054        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20055        "\n\n\n\n\n",
20056        "After folding the third buffer, its text should not be displayed"
20057    );
20058
20059    multi_buffer_editor.update(cx, |editor, cx| {
20060        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20061    });
20062    assert_eq!(
20063        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20064        "\n\n\n\n4444\n5555\n6666\n\n",
20065        "After unfolding the second buffer, its text should be displayed"
20066    );
20067
20068    multi_buffer_editor.update(cx, |editor, cx| {
20069        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20070    });
20071    assert_eq!(
20072        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20073        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20074        "After unfolding the first buffer, its text should be displayed"
20075    );
20076
20077    multi_buffer_editor.update(cx, |editor, cx| {
20078        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20079    });
20080    assert_eq!(
20081        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20082        full_text,
20083        "After unfolding all buffers, all original text should be displayed"
20084    );
20085}
20086
20087#[gpui::test]
20088async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20089    init_test(cx, |_| {});
20090
20091    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20092
20093    let fs = FakeFs::new(cx.executor());
20094    fs.insert_tree(
20095        path!("/a"),
20096        json!({
20097            "main.rs": sample_text,
20098        }),
20099    )
20100    .await;
20101    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20102    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20103    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20104    let worktree = project.update(cx, |project, cx| {
20105        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20106        assert_eq!(worktrees.len(), 1);
20107        worktrees.pop().unwrap()
20108    });
20109    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20110
20111    let buffer_1 = project
20112        .update(cx, |project, cx| {
20113            project.open_buffer((worktree_id, "main.rs"), cx)
20114        })
20115        .await
20116        .unwrap();
20117
20118    let multi_buffer = cx.new(|cx| {
20119        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20120        multi_buffer.push_excerpts(
20121            buffer_1.clone(),
20122            [ExcerptRange::new(
20123                Point::new(0, 0)
20124                    ..Point::new(
20125                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20126                        0,
20127                    ),
20128            )],
20129            cx,
20130        );
20131        multi_buffer
20132    });
20133    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20134        Editor::new(
20135            EditorMode::full(),
20136            multi_buffer,
20137            Some(project.clone()),
20138            window,
20139            cx,
20140        )
20141    });
20142
20143    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20144    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20145        enum TestHighlight {}
20146        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20147        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20148        editor.highlight_text::<TestHighlight>(
20149            vec![highlight_range.clone()],
20150            HighlightStyle::color(Hsla::green()),
20151            cx,
20152        );
20153        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20154            s.select_ranges(Some(highlight_range))
20155        });
20156    });
20157
20158    let full_text = format!("\n\n{sample_text}");
20159    assert_eq!(
20160        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20161        full_text,
20162    );
20163}
20164
20165#[gpui::test]
20166async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20167    init_test(cx, |_| {});
20168    cx.update(|cx| {
20169        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20170            "keymaps/default-linux.json",
20171            cx,
20172        )
20173        .unwrap();
20174        cx.bind_keys(default_key_bindings);
20175    });
20176
20177    let (editor, cx) = cx.add_window_view(|window, cx| {
20178        let multi_buffer = MultiBuffer::build_multi(
20179            [
20180                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20181                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20182                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20183                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20184            ],
20185            cx,
20186        );
20187        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20188
20189        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20190        // fold all but the second buffer, so that we test navigating between two
20191        // adjacent folded buffers, as well as folded buffers at the start and
20192        // end the multibuffer
20193        editor.fold_buffer(buffer_ids[0], cx);
20194        editor.fold_buffer(buffer_ids[2], cx);
20195        editor.fold_buffer(buffer_ids[3], cx);
20196
20197        editor
20198    });
20199    cx.simulate_resize(size(px(1000.), px(1000.)));
20200
20201    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20202    cx.assert_excerpts_with_selections(indoc! {"
20203        [EXCERPT]
20204        ˇ[FOLDED]
20205        [EXCERPT]
20206        a1
20207        b1
20208        [EXCERPT]
20209        [FOLDED]
20210        [EXCERPT]
20211        [FOLDED]
20212        "
20213    });
20214    cx.simulate_keystroke("down");
20215    cx.assert_excerpts_with_selections(indoc! {"
20216        [EXCERPT]
20217        [FOLDED]
20218        [EXCERPT]
20219        ˇa1
20220        b1
20221        [EXCERPT]
20222        [FOLDED]
20223        [EXCERPT]
20224        [FOLDED]
20225        "
20226    });
20227    cx.simulate_keystroke("down");
20228    cx.assert_excerpts_with_selections(indoc! {"
20229        [EXCERPT]
20230        [FOLDED]
20231        [EXCERPT]
20232        a1
20233        ˇb1
20234        [EXCERPT]
20235        [FOLDED]
20236        [EXCERPT]
20237        [FOLDED]
20238        "
20239    });
20240    cx.simulate_keystroke("down");
20241    cx.assert_excerpts_with_selections(indoc! {"
20242        [EXCERPT]
20243        [FOLDED]
20244        [EXCERPT]
20245        a1
20246        b1
20247        ˇ[EXCERPT]
20248        [FOLDED]
20249        [EXCERPT]
20250        [FOLDED]
20251        "
20252    });
20253    cx.simulate_keystroke("down");
20254    cx.assert_excerpts_with_selections(indoc! {"
20255        [EXCERPT]
20256        [FOLDED]
20257        [EXCERPT]
20258        a1
20259        b1
20260        [EXCERPT]
20261        ˇ[FOLDED]
20262        [EXCERPT]
20263        [FOLDED]
20264        "
20265    });
20266    for _ in 0..5 {
20267        cx.simulate_keystroke("down");
20268        cx.assert_excerpts_with_selections(indoc! {"
20269            [EXCERPT]
20270            [FOLDED]
20271            [EXCERPT]
20272            a1
20273            b1
20274            [EXCERPT]
20275            [FOLDED]
20276            [EXCERPT]
20277            ˇ[FOLDED]
20278            "
20279        });
20280    }
20281
20282    cx.simulate_keystroke("up");
20283    cx.assert_excerpts_with_selections(indoc! {"
20284        [EXCERPT]
20285        [FOLDED]
20286        [EXCERPT]
20287        a1
20288        b1
20289        [EXCERPT]
20290        ˇ[FOLDED]
20291        [EXCERPT]
20292        [FOLDED]
20293        "
20294    });
20295    cx.simulate_keystroke("up");
20296    cx.assert_excerpts_with_selections(indoc! {"
20297        [EXCERPT]
20298        [FOLDED]
20299        [EXCERPT]
20300        a1
20301        b1
20302        ˇ[EXCERPT]
20303        [FOLDED]
20304        [EXCERPT]
20305        [FOLDED]
20306        "
20307    });
20308    cx.simulate_keystroke("up");
20309    cx.assert_excerpts_with_selections(indoc! {"
20310        [EXCERPT]
20311        [FOLDED]
20312        [EXCERPT]
20313        a1
20314        ˇb1
20315        [EXCERPT]
20316        [FOLDED]
20317        [EXCERPT]
20318        [FOLDED]
20319        "
20320    });
20321    cx.simulate_keystroke("up");
20322    cx.assert_excerpts_with_selections(indoc! {"
20323        [EXCERPT]
20324        [FOLDED]
20325        [EXCERPT]
20326        ˇa1
20327        b1
20328        [EXCERPT]
20329        [FOLDED]
20330        [EXCERPT]
20331        [FOLDED]
20332        "
20333    });
20334    for _ in 0..5 {
20335        cx.simulate_keystroke("up");
20336        cx.assert_excerpts_with_selections(indoc! {"
20337            [EXCERPT]
20338            ˇ[FOLDED]
20339            [EXCERPT]
20340            a1
20341            b1
20342            [EXCERPT]
20343            [FOLDED]
20344            [EXCERPT]
20345            [FOLDED]
20346            "
20347        });
20348    }
20349}
20350
20351#[gpui::test]
20352async fn test_inline_completion_text(cx: &mut TestAppContext) {
20353    init_test(cx, |_| {});
20354
20355    // Simple insertion
20356    assert_highlighted_edits(
20357        "Hello, world!",
20358        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20359        true,
20360        cx,
20361        |highlighted_edits, cx| {
20362            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20363            assert_eq!(highlighted_edits.highlights.len(), 1);
20364            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20365            assert_eq!(
20366                highlighted_edits.highlights[0].1.background_color,
20367                Some(cx.theme().status().created_background)
20368            );
20369        },
20370    )
20371    .await;
20372
20373    // Replacement
20374    assert_highlighted_edits(
20375        "This is a test.",
20376        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20377        false,
20378        cx,
20379        |highlighted_edits, cx| {
20380            assert_eq!(highlighted_edits.text, "That is a test.");
20381            assert_eq!(highlighted_edits.highlights.len(), 1);
20382            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20383            assert_eq!(
20384                highlighted_edits.highlights[0].1.background_color,
20385                Some(cx.theme().status().created_background)
20386            );
20387        },
20388    )
20389    .await;
20390
20391    // Multiple edits
20392    assert_highlighted_edits(
20393        "Hello, world!",
20394        vec![
20395            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20396            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20397        ],
20398        false,
20399        cx,
20400        |highlighted_edits, cx| {
20401            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20402            assert_eq!(highlighted_edits.highlights.len(), 2);
20403            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20404            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20405            assert_eq!(
20406                highlighted_edits.highlights[0].1.background_color,
20407                Some(cx.theme().status().created_background)
20408            );
20409            assert_eq!(
20410                highlighted_edits.highlights[1].1.background_color,
20411                Some(cx.theme().status().created_background)
20412            );
20413        },
20414    )
20415    .await;
20416
20417    // Multiple lines with edits
20418    assert_highlighted_edits(
20419        "First line\nSecond line\nThird line\nFourth line",
20420        vec![
20421            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20422            (
20423                Point::new(2, 0)..Point::new(2, 10),
20424                "New third line".to_string(),
20425            ),
20426            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20427        ],
20428        false,
20429        cx,
20430        |highlighted_edits, cx| {
20431            assert_eq!(
20432                highlighted_edits.text,
20433                "Second modified\nNew third line\nFourth updated line"
20434            );
20435            assert_eq!(highlighted_edits.highlights.len(), 3);
20436            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20437            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20438            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20439            for highlight in &highlighted_edits.highlights {
20440                assert_eq!(
20441                    highlight.1.background_color,
20442                    Some(cx.theme().status().created_background)
20443                );
20444            }
20445        },
20446    )
20447    .await;
20448}
20449
20450#[gpui::test]
20451async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20452    init_test(cx, |_| {});
20453
20454    // Deletion
20455    assert_highlighted_edits(
20456        "Hello, world!",
20457        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20458        true,
20459        cx,
20460        |highlighted_edits, cx| {
20461            assert_eq!(highlighted_edits.text, "Hello, world!");
20462            assert_eq!(highlighted_edits.highlights.len(), 1);
20463            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20464            assert_eq!(
20465                highlighted_edits.highlights[0].1.background_color,
20466                Some(cx.theme().status().deleted_background)
20467            );
20468        },
20469    )
20470    .await;
20471
20472    // Insertion
20473    assert_highlighted_edits(
20474        "Hello, world!",
20475        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20476        true,
20477        cx,
20478        |highlighted_edits, cx| {
20479            assert_eq!(highlighted_edits.highlights.len(), 1);
20480            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20481            assert_eq!(
20482                highlighted_edits.highlights[0].1.background_color,
20483                Some(cx.theme().status().created_background)
20484            );
20485        },
20486    )
20487    .await;
20488}
20489
20490async fn assert_highlighted_edits(
20491    text: &str,
20492    edits: Vec<(Range<Point>, String)>,
20493    include_deletions: bool,
20494    cx: &mut TestAppContext,
20495    assertion_fn: impl Fn(HighlightedText, &App),
20496) {
20497    let window = cx.add_window(|window, cx| {
20498        let buffer = MultiBuffer::build_simple(text, cx);
20499        Editor::new(EditorMode::full(), buffer, None, window, cx)
20500    });
20501    let cx = &mut VisualTestContext::from_window(*window, cx);
20502
20503    let (buffer, snapshot) = window
20504        .update(cx, |editor, _window, cx| {
20505            (
20506                editor.buffer().clone(),
20507                editor.buffer().read(cx).snapshot(cx),
20508            )
20509        })
20510        .unwrap();
20511
20512    let edits = edits
20513        .into_iter()
20514        .map(|(range, edit)| {
20515            (
20516                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20517                edit,
20518            )
20519        })
20520        .collect::<Vec<_>>();
20521
20522    let text_anchor_edits = edits
20523        .clone()
20524        .into_iter()
20525        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20526        .collect::<Vec<_>>();
20527
20528    let edit_preview = window
20529        .update(cx, |_, _window, cx| {
20530            buffer
20531                .read(cx)
20532                .as_singleton()
20533                .unwrap()
20534                .read(cx)
20535                .preview_edits(text_anchor_edits.into(), cx)
20536        })
20537        .unwrap()
20538        .await;
20539
20540    cx.update(|_window, cx| {
20541        let highlighted_edits = inline_completion_edit_text(
20542            &snapshot.as_singleton().unwrap().2,
20543            &edits,
20544            &edit_preview,
20545            include_deletions,
20546            cx,
20547        );
20548        assertion_fn(highlighted_edits, cx)
20549    });
20550}
20551
20552#[track_caller]
20553fn assert_breakpoint(
20554    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20555    path: &Arc<Path>,
20556    expected: Vec<(u32, Breakpoint)>,
20557) {
20558    if expected.len() == 0usize {
20559        assert!(!breakpoints.contains_key(path), "{}", path.display());
20560    } else {
20561        let mut breakpoint = breakpoints
20562            .get(path)
20563            .unwrap()
20564            .into_iter()
20565            .map(|breakpoint| {
20566                (
20567                    breakpoint.row,
20568                    Breakpoint {
20569                        message: breakpoint.message.clone(),
20570                        state: breakpoint.state,
20571                        condition: breakpoint.condition.clone(),
20572                        hit_condition: breakpoint.hit_condition.clone(),
20573                    },
20574                )
20575            })
20576            .collect::<Vec<_>>();
20577
20578        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20579
20580        assert_eq!(expected, breakpoint);
20581    }
20582}
20583
20584fn add_log_breakpoint_at_cursor(
20585    editor: &mut Editor,
20586    log_message: &str,
20587    window: &mut Window,
20588    cx: &mut Context<Editor>,
20589) {
20590    let (anchor, bp) = editor
20591        .breakpoints_at_cursors(window, cx)
20592        .first()
20593        .and_then(|(anchor, bp)| {
20594            if let Some(bp) = bp {
20595                Some((*anchor, bp.clone()))
20596            } else {
20597                None
20598            }
20599        })
20600        .unwrap_or_else(|| {
20601            let cursor_position: Point = editor.selections.newest(cx).head();
20602
20603            let breakpoint_position = editor
20604                .snapshot(window, cx)
20605                .display_snapshot
20606                .buffer_snapshot
20607                .anchor_before(Point::new(cursor_position.row, 0));
20608
20609            (breakpoint_position, Breakpoint::new_log(&log_message))
20610        });
20611
20612    editor.edit_breakpoint_at_anchor(
20613        anchor,
20614        bp,
20615        BreakpointEditAction::EditLogMessage(log_message.into()),
20616        cx,
20617    );
20618}
20619
20620#[gpui::test]
20621async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20622    init_test(cx, |_| {});
20623
20624    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20625    let fs = FakeFs::new(cx.executor());
20626    fs.insert_tree(
20627        path!("/a"),
20628        json!({
20629            "main.rs": sample_text,
20630        }),
20631    )
20632    .await;
20633    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20634    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20635    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20636
20637    let fs = FakeFs::new(cx.executor());
20638    fs.insert_tree(
20639        path!("/a"),
20640        json!({
20641            "main.rs": sample_text,
20642        }),
20643    )
20644    .await;
20645    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20646    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20647    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20648    let worktree_id = workspace
20649        .update(cx, |workspace, _window, cx| {
20650            workspace.project().update(cx, |project, cx| {
20651                project.worktrees(cx).next().unwrap().read(cx).id()
20652            })
20653        })
20654        .unwrap();
20655
20656    let buffer = project
20657        .update(cx, |project, cx| {
20658            project.open_buffer((worktree_id, "main.rs"), cx)
20659        })
20660        .await
20661        .unwrap();
20662
20663    let (editor, cx) = cx.add_window_view(|window, cx| {
20664        Editor::new(
20665            EditorMode::full(),
20666            MultiBuffer::build_from_buffer(buffer, cx),
20667            Some(project.clone()),
20668            window,
20669            cx,
20670        )
20671    });
20672
20673    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20674    let abs_path = project.read_with(cx, |project, cx| {
20675        project
20676            .absolute_path(&project_path, cx)
20677            .map(|path_buf| Arc::from(path_buf.to_owned()))
20678            .unwrap()
20679    });
20680
20681    // assert we can add breakpoint on the first line
20682    editor.update_in(cx, |editor, window, cx| {
20683        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20684        editor.move_to_end(&MoveToEnd, window, cx);
20685        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20686    });
20687
20688    let breakpoints = editor.update(cx, |editor, cx| {
20689        editor
20690            .breakpoint_store()
20691            .as_ref()
20692            .unwrap()
20693            .read(cx)
20694            .all_source_breakpoints(cx)
20695            .clone()
20696    });
20697
20698    assert_eq!(1, breakpoints.len());
20699    assert_breakpoint(
20700        &breakpoints,
20701        &abs_path,
20702        vec![
20703            (0, Breakpoint::new_standard()),
20704            (3, Breakpoint::new_standard()),
20705        ],
20706    );
20707
20708    editor.update_in(cx, |editor, window, cx| {
20709        editor.move_to_beginning(&MoveToBeginning, window, cx);
20710        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20711    });
20712
20713    let breakpoints = editor.update(cx, |editor, cx| {
20714        editor
20715            .breakpoint_store()
20716            .as_ref()
20717            .unwrap()
20718            .read(cx)
20719            .all_source_breakpoints(cx)
20720            .clone()
20721    });
20722
20723    assert_eq!(1, breakpoints.len());
20724    assert_breakpoint(
20725        &breakpoints,
20726        &abs_path,
20727        vec![(3, Breakpoint::new_standard())],
20728    );
20729
20730    editor.update_in(cx, |editor, window, cx| {
20731        editor.move_to_end(&MoveToEnd, window, cx);
20732        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20733    });
20734
20735    let breakpoints = editor.update(cx, |editor, cx| {
20736        editor
20737            .breakpoint_store()
20738            .as_ref()
20739            .unwrap()
20740            .read(cx)
20741            .all_source_breakpoints(cx)
20742            .clone()
20743    });
20744
20745    assert_eq!(0, breakpoints.len());
20746    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20747}
20748
20749#[gpui::test]
20750async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20751    init_test(cx, |_| {});
20752
20753    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20754
20755    let fs = FakeFs::new(cx.executor());
20756    fs.insert_tree(
20757        path!("/a"),
20758        json!({
20759            "main.rs": sample_text,
20760        }),
20761    )
20762    .await;
20763    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20764    let (workspace, cx) =
20765        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20766
20767    let worktree_id = workspace.update(cx, |workspace, cx| {
20768        workspace.project().update(cx, |project, cx| {
20769            project.worktrees(cx).next().unwrap().read(cx).id()
20770        })
20771    });
20772
20773    let buffer = project
20774        .update(cx, |project, cx| {
20775            project.open_buffer((worktree_id, "main.rs"), cx)
20776        })
20777        .await
20778        .unwrap();
20779
20780    let (editor, cx) = cx.add_window_view(|window, cx| {
20781        Editor::new(
20782            EditorMode::full(),
20783            MultiBuffer::build_from_buffer(buffer, cx),
20784            Some(project.clone()),
20785            window,
20786            cx,
20787        )
20788    });
20789
20790    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20791    let abs_path = project.read_with(cx, |project, cx| {
20792        project
20793            .absolute_path(&project_path, cx)
20794            .map(|path_buf| Arc::from(path_buf.to_owned()))
20795            .unwrap()
20796    });
20797
20798    editor.update_in(cx, |editor, window, cx| {
20799        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20800    });
20801
20802    let breakpoints = editor.update(cx, |editor, cx| {
20803        editor
20804            .breakpoint_store()
20805            .as_ref()
20806            .unwrap()
20807            .read(cx)
20808            .all_source_breakpoints(cx)
20809            .clone()
20810    });
20811
20812    assert_breakpoint(
20813        &breakpoints,
20814        &abs_path,
20815        vec![(0, Breakpoint::new_log("hello world"))],
20816    );
20817
20818    // Removing a log message from a log breakpoint should remove it
20819    editor.update_in(cx, |editor, window, cx| {
20820        add_log_breakpoint_at_cursor(editor, "", window, cx);
20821    });
20822
20823    let breakpoints = editor.update(cx, |editor, cx| {
20824        editor
20825            .breakpoint_store()
20826            .as_ref()
20827            .unwrap()
20828            .read(cx)
20829            .all_source_breakpoints(cx)
20830            .clone()
20831    });
20832
20833    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20834
20835    editor.update_in(cx, |editor, window, cx| {
20836        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20837        editor.move_to_end(&MoveToEnd, window, cx);
20838        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20839        // Not adding a log message to a standard breakpoint shouldn't remove it
20840        add_log_breakpoint_at_cursor(editor, "", window, cx);
20841    });
20842
20843    let breakpoints = editor.update(cx, |editor, cx| {
20844        editor
20845            .breakpoint_store()
20846            .as_ref()
20847            .unwrap()
20848            .read(cx)
20849            .all_source_breakpoints(cx)
20850            .clone()
20851    });
20852
20853    assert_breakpoint(
20854        &breakpoints,
20855        &abs_path,
20856        vec![
20857            (0, Breakpoint::new_standard()),
20858            (3, Breakpoint::new_standard()),
20859        ],
20860    );
20861
20862    editor.update_in(cx, |editor, window, cx| {
20863        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20864    });
20865
20866    let breakpoints = editor.update(cx, |editor, cx| {
20867        editor
20868            .breakpoint_store()
20869            .as_ref()
20870            .unwrap()
20871            .read(cx)
20872            .all_source_breakpoints(cx)
20873            .clone()
20874    });
20875
20876    assert_breakpoint(
20877        &breakpoints,
20878        &abs_path,
20879        vec![
20880            (0, Breakpoint::new_standard()),
20881            (3, Breakpoint::new_log("hello world")),
20882        ],
20883    );
20884
20885    editor.update_in(cx, |editor, window, cx| {
20886        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20887    });
20888
20889    let breakpoints = editor.update(cx, |editor, cx| {
20890        editor
20891            .breakpoint_store()
20892            .as_ref()
20893            .unwrap()
20894            .read(cx)
20895            .all_source_breakpoints(cx)
20896            .clone()
20897    });
20898
20899    assert_breakpoint(
20900        &breakpoints,
20901        &abs_path,
20902        vec![
20903            (0, Breakpoint::new_standard()),
20904            (3, Breakpoint::new_log("hello Earth!!")),
20905        ],
20906    );
20907}
20908
20909/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20910/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20911/// or when breakpoints were placed out of order. This tests for a regression too
20912#[gpui::test]
20913async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20914    init_test(cx, |_| {});
20915
20916    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20917    let fs = FakeFs::new(cx.executor());
20918    fs.insert_tree(
20919        path!("/a"),
20920        json!({
20921            "main.rs": sample_text,
20922        }),
20923    )
20924    .await;
20925    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20926    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20927    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20928
20929    let fs = FakeFs::new(cx.executor());
20930    fs.insert_tree(
20931        path!("/a"),
20932        json!({
20933            "main.rs": sample_text,
20934        }),
20935    )
20936    .await;
20937    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20938    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20939    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20940    let worktree_id = workspace
20941        .update(cx, |workspace, _window, cx| {
20942            workspace.project().update(cx, |project, cx| {
20943                project.worktrees(cx).next().unwrap().read(cx).id()
20944            })
20945        })
20946        .unwrap();
20947
20948    let buffer = project
20949        .update(cx, |project, cx| {
20950            project.open_buffer((worktree_id, "main.rs"), cx)
20951        })
20952        .await
20953        .unwrap();
20954
20955    let (editor, cx) = cx.add_window_view(|window, cx| {
20956        Editor::new(
20957            EditorMode::full(),
20958            MultiBuffer::build_from_buffer(buffer, cx),
20959            Some(project.clone()),
20960            window,
20961            cx,
20962        )
20963    });
20964
20965    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20966    let abs_path = project.read_with(cx, |project, cx| {
20967        project
20968            .absolute_path(&project_path, cx)
20969            .map(|path_buf| Arc::from(path_buf.to_owned()))
20970            .unwrap()
20971    });
20972
20973    // assert we can add breakpoint on the first line
20974    editor.update_in(cx, |editor, window, cx| {
20975        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20976        editor.move_to_end(&MoveToEnd, window, cx);
20977        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20978        editor.move_up(&MoveUp, window, cx);
20979        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20980    });
20981
20982    let breakpoints = editor.update(cx, |editor, cx| {
20983        editor
20984            .breakpoint_store()
20985            .as_ref()
20986            .unwrap()
20987            .read(cx)
20988            .all_source_breakpoints(cx)
20989            .clone()
20990    });
20991
20992    assert_eq!(1, breakpoints.len());
20993    assert_breakpoint(
20994        &breakpoints,
20995        &abs_path,
20996        vec![
20997            (0, Breakpoint::new_standard()),
20998            (2, Breakpoint::new_standard()),
20999            (3, Breakpoint::new_standard()),
21000        ],
21001    );
21002
21003    editor.update_in(cx, |editor, window, cx| {
21004        editor.move_to_beginning(&MoveToBeginning, window, cx);
21005        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21006        editor.move_to_end(&MoveToEnd, window, cx);
21007        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21008        // Disabling a breakpoint that doesn't exist should do nothing
21009        editor.move_up(&MoveUp, window, cx);
21010        editor.move_up(&MoveUp, window, cx);
21011        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21012    });
21013
21014    let breakpoints = editor.update(cx, |editor, cx| {
21015        editor
21016            .breakpoint_store()
21017            .as_ref()
21018            .unwrap()
21019            .read(cx)
21020            .all_source_breakpoints(cx)
21021            .clone()
21022    });
21023
21024    let disable_breakpoint = {
21025        let mut bp = Breakpoint::new_standard();
21026        bp.state = BreakpointState::Disabled;
21027        bp
21028    };
21029
21030    assert_eq!(1, breakpoints.len());
21031    assert_breakpoint(
21032        &breakpoints,
21033        &abs_path,
21034        vec![
21035            (0, disable_breakpoint.clone()),
21036            (2, Breakpoint::new_standard()),
21037            (3, disable_breakpoint.clone()),
21038        ],
21039    );
21040
21041    editor.update_in(cx, |editor, window, cx| {
21042        editor.move_to_beginning(&MoveToBeginning, window, cx);
21043        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21044        editor.move_to_end(&MoveToEnd, window, cx);
21045        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21046        editor.move_up(&MoveUp, window, cx);
21047        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21048    });
21049
21050    let breakpoints = editor.update(cx, |editor, cx| {
21051        editor
21052            .breakpoint_store()
21053            .as_ref()
21054            .unwrap()
21055            .read(cx)
21056            .all_source_breakpoints(cx)
21057            .clone()
21058    });
21059
21060    assert_eq!(1, breakpoints.len());
21061    assert_breakpoint(
21062        &breakpoints,
21063        &abs_path,
21064        vec![
21065            (0, Breakpoint::new_standard()),
21066            (2, disable_breakpoint),
21067            (3, Breakpoint::new_standard()),
21068        ],
21069    );
21070}
21071
21072#[gpui::test]
21073async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21074    init_test(cx, |_| {});
21075    let capabilities = lsp::ServerCapabilities {
21076        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21077            prepare_provider: Some(true),
21078            work_done_progress_options: Default::default(),
21079        })),
21080        ..Default::default()
21081    };
21082    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21083
21084    cx.set_state(indoc! {"
21085        struct Fˇoo {}
21086    "});
21087
21088    cx.update_editor(|editor, _, cx| {
21089        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21090        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21091        editor.highlight_background::<DocumentHighlightRead>(
21092            &[highlight_range],
21093            |theme| theme.colors().editor_document_highlight_read_background,
21094            cx,
21095        );
21096    });
21097
21098    let mut prepare_rename_handler = cx
21099        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21100            move |_, _, _| async move {
21101                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21102                    start: lsp::Position {
21103                        line: 0,
21104                        character: 7,
21105                    },
21106                    end: lsp::Position {
21107                        line: 0,
21108                        character: 10,
21109                    },
21110                })))
21111            },
21112        );
21113    let prepare_rename_task = cx
21114        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21115        .expect("Prepare rename was not started");
21116    prepare_rename_handler.next().await.unwrap();
21117    prepare_rename_task.await.expect("Prepare rename failed");
21118
21119    let mut rename_handler =
21120        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21121            let edit = lsp::TextEdit {
21122                range: lsp::Range {
21123                    start: lsp::Position {
21124                        line: 0,
21125                        character: 7,
21126                    },
21127                    end: lsp::Position {
21128                        line: 0,
21129                        character: 10,
21130                    },
21131                },
21132                new_text: "FooRenamed".to_string(),
21133            };
21134            Ok(Some(lsp::WorkspaceEdit::new(
21135                // Specify the same edit twice
21136                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21137            )))
21138        });
21139    let rename_task = cx
21140        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21141        .expect("Confirm rename was not started");
21142    rename_handler.next().await.unwrap();
21143    rename_task.await.expect("Confirm rename failed");
21144    cx.run_until_parked();
21145
21146    // Despite two edits, only one is actually applied as those are identical
21147    cx.assert_editor_state(indoc! {"
21148        struct FooRenamedˇ {}
21149    "});
21150}
21151
21152#[gpui::test]
21153async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21154    init_test(cx, |_| {});
21155    // These capabilities indicate that the server does not support prepare rename.
21156    let capabilities = lsp::ServerCapabilities {
21157        rename_provider: Some(lsp::OneOf::Left(true)),
21158        ..Default::default()
21159    };
21160    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21161
21162    cx.set_state(indoc! {"
21163        struct Fˇoo {}
21164    "});
21165
21166    cx.update_editor(|editor, _window, cx| {
21167        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21168        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21169        editor.highlight_background::<DocumentHighlightRead>(
21170            &[highlight_range],
21171            |theme| theme.colors().editor_document_highlight_read_background,
21172            cx,
21173        );
21174    });
21175
21176    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21177        .expect("Prepare rename was not started")
21178        .await
21179        .expect("Prepare rename failed");
21180
21181    let mut rename_handler =
21182        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21183            let edit = lsp::TextEdit {
21184                range: lsp::Range {
21185                    start: lsp::Position {
21186                        line: 0,
21187                        character: 7,
21188                    },
21189                    end: lsp::Position {
21190                        line: 0,
21191                        character: 10,
21192                    },
21193                },
21194                new_text: "FooRenamed".to_string(),
21195            };
21196            Ok(Some(lsp::WorkspaceEdit::new(
21197                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21198            )))
21199        });
21200    let rename_task = cx
21201        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21202        .expect("Confirm rename was not started");
21203    rename_handler.next().await.unwrap();
21204    rename_task.await.expect("Confirm rename failed");
21205    cx.run_until_parked();
21206
21207    // Correct range is renamed, as `surrounding_word` is used to find it.
21208    cx.assert_editor_state(indoc! {"
21209        struct FooRenamedˇ {}
21210    "});
21211}
21212
21213#[gpui::test]
21214async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21215    init_test(cx, |_| {});
21216    let mut cx = EditorTestContext::new(cx).await;
21217
21218    let language = Arc::new(
21219        Language::new(
21220            LanguageConfig::default(),
21221            Some(tree_sitter_html::LANGUAGE.into()),
21222        )
21223        .with_brackets_query(
21224            r#"
21225            ("<" @open "/>" @close)
21226            ("</" @open ">" @close)
21227            ("<" @open ">" @close)
21228            ("\"" @open "\"" @close)
21229            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21230        "#,
21231        )
21232        .unwrap(),
21233    );
21234    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21235
21236    cx.set_state(indoc! {"
21237        <span>ˇ</span>
21238    "});
21239    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21240    cx.assert_editor_state(indoc! {"
21241        <span>
21242        ˇ
21243        </span>
21244    "});
21245
21246    cx.set_state(indoc! {"
21247        <span><span></span>ˇ</span>
21248    "});
21249    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21250    cx.assert_editor_state(indoc! {"
21251        <span><span></span>
21252        ˇ</span>
21253    "});
21254
21255    cx.set_state(indoc! {"
21256        <span>ˇ
21257        </span>
21258    "});
21259    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21260    cx.assert_editor_state(indoc! {"
21261        <span>
21262        ˇ
21263        </span>
21264    "});
21265}
21266
21267#[gpui::test(iterations = 10)]
21268async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21269    init_test(cx, |_| {});
21270
21271    let fs = FakeFs::new(cx.executor());
21272    fs.insert_tree(
21273        path!("/dir"),
21274        json!({
21275            "a.ts": "a",
21276        }),
21277    )
21278    .await;
21279
21280    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21281    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21282    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21283
21284    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21285    language_registry.add(Arc::new(Language::new(
21286        LanguageConfig {
21287            name: "TypeScript".into(),
21288            matcher: LanguageMatcher {
21289                path_suffixes: vec!["ts".to_string()],
21290                ..Default::default()
21291            },
21292            ..Default::default()
21293        },
21294        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21295    )));
21296    let mut fake_language_servers = language_registry.register_fake_lsp(
21297        "TypeScript",
21298        FakeLspAdapter {
21299            capabilities: lsp::ServerCapabilities {
21300                code_lens_provider: Some(lsp::CodeLensOptions {
21301                    resolve_provider: Some(true),
21302                }),
21303                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21304                    commands: vec!["_the/command".to_string()],
21305                    ..lsp::ExecuteCommandOptions::default()
21306                }),
21307                ..lsp::ServerCapabilities::default()
21308            },
21309            ..FakeLspAdapter::default()
21310        },
21311    );
21312
21313    let (buffer, _handle) = project
21314        .update(cx, |p, cx| {
21315            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21316        })
21317        .await
21318        .unwrap();
21319    cx.executor().run_until_parked();
21320
21321    let fake_server = fake_language_servers.next().await.unwrap();
21322
21323    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21324    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21325    drop(buffer_snapshot);
21326    let actions = cx
21327        .update_window(*workspace, |_, window, cx| {
21328            project.code_actions(&buffer, anchor..anchor, window, cx)
21329        })
21330        .unwrap();
21331
21332    fake_server
21333        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21334            Ok(Some(vec![
21335                lsp::CodeLens {
21336                    range: lsp::Range::default(),
21337                    command: Some(lsp::Command {
21338                        title: "Code lens command".to_owned(),
21339                        command: "_the/command".to_owned(),
21340                        arguments: None,
21341                    }),
21342                    data: None,
21343                },
21344                lsp::CodeLens {
21345                    range: lsp::Range::default(),
21346                    command: Some(lsp::Command {
21347                        title: "Command not in capabilities".to_owned(),
21348                        command: "not in capabilities".to_owned(),
21349                        arguments: None,
21350                    }),
21351                    data: None,
21352                },
21353                lsp::CodeLens {
21354                    range: lsp::Range {
21355                        start: lsp::Position {
21356                            line: 1,
21357                            character: 1,
21358                        },
21359                        end: lsp::Position {
21360                            line: 1,
21361                            character: 1,
21362                        },
21363                    },
21364                    command: Some(lsp::Command {
21365                        title: "Command not in range".to_owned(),
21366                        command: "_the/command".to_owned(),
21367                        arguments: None,
21368                    }),
21369                    data: None,
21370                },
21371            ]))
21372        })
21373        .next()
21374        .await;
21375
21376    let actions = actions.await.unwrap();
21377    assert_eq!(
21378        actions.len(),
21379        1,
21380        "Should have only one valid action for the 0..0 range"
21381    );
21382    let action = actions[0].clone();
21383    let apply = project.update(cx, |project, cx| {
21384        project.apply_code_action(buffer.clone(), action, true, cx)
21385    });
21386
21387    // Resolving the code action does not populate its edits. In absence of
21388    // edits, we must execute the given command.
21389    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21390        |mut lens, _| async move {
21391            let lens_command = lens.command.as_mut().expect("should have a command");
21392            assert_eq!(lens_command.title, "Code lens command");
21393            lens_command.arguments = Some(vec![json!("the-argument")]);
21394            Ok(lens)
21395        },
21396    );
21397
21398    // While executing the command, the language server sends the editor
21399    // a `workspaceEdit` request.
21400    fake_server
21401        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21402            let fake = fake_server.clone();
21403            move |params, _| {
21404                assert_eq!(params.command, "_the/command");
21405                let fake = fake.clone();
21406                async move {
21407                    fake.server
21408                        .request::<lsp::request::ApplyWorkspaceEdit>(
21409                            lsp::ApplyWorkspaceEditParams {
21410                                label: None,
21411                                edit: lsp::WorkspaceEdit {
21412                                    changes: Some(
21413                                        [(
21414                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21415                                            vec![lsp::TextEdit {
21416                                                range: lsp::Range::new(
21417                                                    lsp::Position::new(0, 0),
21418                                                    lsp::Position::new(0, 0),
21419                                                ),
21420                                                new_text: "X".into(),
21421                                            }],
21422                                        )]
21423                                        .into_iter()
21424                                        .collect(),
21425                                    ),
21426                                    ..Default::default()
21427                                },
21428                            },
21429                        )
21430                        .await
21431                        .into_response()
21432                        .unwrap();
21433                    Ok(Some(json!(null)))
21434                }
21435            }
21436        })
21437        .next()
21438        .await;
21439
21440    // Applying the code lens command returns a project transaction containing the edits
21441    // sent by the language server in its `workspaceEdit` request.
21442    let transaction = apply.await.unwrap();
21443    assert!(transaction.0.contains_key(&buffer));
21444    buffer.update(cx, |buffer, cx| {
21445        assert_eq!(buffer.text(), "Xa");
21446        buffer.undo(cx);
21447        assert_eq!(buffer.text(), "a");
21448    });
21449}
21450
21451#[gpui::test]
21452async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21453    init_test(cx, |_| {});
21454
21455    let fs = FakeFs::new(cx.executor());
21456    let main_text = r#"fn main() {
21457println!("1");
21458println!("2");
21459println!("3");
21460println!("4");
21461println!("5");
21462}"#;
21463    let lib_text = "mod foo {}";
21464    fs.insert_tree(
21465        path!("/a"),
21466        json!({
21467            "lib.rs": lib_text,
21468            "main.rs": main_text,
21469        }),
21470    )
21471    .await;
21472
21473    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21474    let (workspace, cx) =
21475        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21476    let worktree_id = workspace.update(cx, |workspace, cx| {
21477        workspace.project().update(cx, |project, cx| {
21478            project.worktrees(cx).next().unwrap().read(cx).id()
21479        })
21480    });
21481
21482    let expected_ranges = vec![
21483        Point::new(0, 0)..Point::new(0, 0),
21484        Point::new(1, 0)..Point::new(1, 1),
21485        Point::new(2, 0)..Point::new(2, 2),
21486        Point::new(3, 0)..Point::new(3, 3),
21487    ];
21488
21489    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21490    let editor_1 = workspace
21491        .update_in(cx, |workspace, window, cx| {
21492            workspace.open_path(
21493                (worktree_id, "main.rs"),
21494                Some(pane_1.downgrade()),
21495                true,
21496                window,
21497                cx,
21498            )
21499        })
21500        .unwrap()
21501        .await
21502        .downcast::<Editor>()
21503        .unwrap();
21504    pane_1.update(cx, |pane, cx| {
21505        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21506        open_editor.update(cx, |editor, cx| {
21507            assert_eq!(
21508                editor.display_text(cx),
21509                main_text,
21510                "Original main.rs text on initial open",
21511            );
21512            assert_eq!(
21513                editor
21514                    .selections
21515                    .all::<Point>(cx)
21516                    .into_iter()
21517                    .map(|s| s.range())
21518                    .collect::<Vec<_>>(),
21519                vec![Point::zero()..Point::zero()],
21520                "Default selections on initial open",
21521            );
21522        })
21523    });
21524    editor_1.update_in(cx, |editor, window, cx| {
21525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21526            s.select_ranges(expected_ranges.clone());
21527        });
21528    });
21529
21530    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21531        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21532    });
21533    let editor_2 = workspace
21534        .update_in(cx, |workspace, window, cx| {
21535            workspace.open_path(
21536                (worktree_id, "main.rs"),
21537                Some(pane_2.downgrade()),
21538                true,
21539                window,
21540                cx,
21541            )
21542        })
21543        .unwrap()
21544        .await
21545        .downcast::<Editor>()
21546        .unwrap();
21547    pane_2.update(cx, |pane, cx| {
21548        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21549        open_editor.update(cx, |editor, cx| {
21550            assert_eq!(
21551                editor.display_text(cx),
21552                main_text,
21553                "Original main.rs text on initial open in another panel",
21554            );
21555            assert_eq!(
21556                editor
21557                    .selections
21558                    .all::<Point>(cx)
21559                    .into_iter()
21560                    .map(|s| s.range())
21561                    .collect::<Vec<_>>(),
21562                vec![Point::zero()..Point::zero()],
21563                "Default selections on initial open in another panel",
21564            );
21565        })
21566    });
21567
21568    editor_2.update_in(cx, |editor, window, cx| {
21569        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21570    });
21571
21572    let _other_editor_1 = workspace
21573        .update_in(cx, |workspace, window, cx| {
21574            workspace.open_path(
21575                (worktree_id, "lib.rs"),
21576                Some(pane_1.downgrade()),
21577                true,
21578                window,
21579                cx,
21580            )
21581        })
21582        .unwrap()
21583        .await
21584        .downcast::<Editor>()
21585        .unwrap();
21586    pane_1
21587        .update_in(cx, |pane, window, cx| {
21588            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21589        })
21590        .await
21591        .unwrap();
21592    drop(editor_1);
21593    pane_1.update(cx, |pane, cx| {
21594        pane.active_item()
21595            .unwrap()
21596            .downcast::<Editor>()
21597            .unwrap()
21598            .update(cx, |editor, cx| {
21599                assert_eq!(
21600                    editor.display_text(cx),
21601                    lib_text,
21602                    "Other file should be open and active",
21603                );
21604            });
21605        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21606    });
21607
21608    let _other_editor_2 = workspace
21609        .update_in(cx, |workspace, window, cx| {
21610            workspace.open_path(
21611                (worktree_id, "lib.rs"),
21612                Some(pane_2.downgrade()),
21613                true,
21614                window,
21615                cx,
21616            )
21617        })
21618        .unwrap()
21619        .await
21620        .downcast::<Editor>()
21621        .unwrap();
21622    pane_2
21623        .update_in(cx, |pane, window, cx| {
21624            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21625        })
21626        .await
21627        .unwrap();
21628    drop(editor_2);
21629    pane_2.update(cx, |pane, cx| {
21630        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21631        open_editor.update(cx, |editor, cx| {
21632            assert_eq!(
21633                editor.display_text(cx),
21634                lib_text,
21635                "Other file should be open and active in another panel too",
21636            );
21637        });
21638        assert_eq!(
21639            pane.items().count(),
21640            1,
21641            "No other editors should be open in another pane",
21642        );
21643    });
21644
21645    let _editor_1_reopened = workspace
21646        .update_in(cx, |workspace, window, cx| {
21647            workspace.open_path(
21648                (worktree_id, "main.rs"),
21649                Some(pane_1.downgrade()),
21650                true,
21651                window,
21652                cx,
21653            )
21654        })
21655        .unwrap()
21656        .await
21657        .downcast::<Editor>()
21658        .unwrap();
21659    let _editor_2_reopened = workspace
21660        .update_in(cx, |workspace, window, cx| {
21661            workspace.open_path(
21662                (worktree_id, "main.rs"),
21663                Some(pane_2.downgrade()),
21664                true,
21665                window,
21666                cx,
21667            )
21668        })
21669        .unwrap()
21670        .await
21671        .downcast::<Editor>()
21672        .unwrap();
21673    pane_1.update(cx, |pane, cx| {
21674        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21675        open_editor.update(cx, |editor, cx| {
21676            assert_eq!(
21677                editor.display_text(cx),
21678                main_text,
21679                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21680            );
21681            assert_eq!(
21682                editor
21683                    .selections
21684                    .all::<Point>(cx)
21685                    .into_iter()
21686                    .map(|s| s.range())
21687                    .collect::<Vec<_>>(),
21688                expected_ranges,
21689                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21690            );
21691        })
21692    });
21693    pane_2.update(cx, |pane, cx| {
21694        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21695        open_editor.update(cx, |editor, cx| {
21696            assert_eq!(
21697                editor.display_text(cx),
21698                r#"fn main() {
21699⋯rintln!("1");
21700⋯intln!("2");
21701⋯ntln!("3");
21702println!("4");
21703println!("5");
21704}"#,
21705                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21706            );
21707            assert_eq!(
21708                editor
21709                    .selections
21710                    .all::<Point>(cx)
21711                    .into_iter()
21712                    .map(|s| s.range())
21713                    .collect::<Vec<_>>(),
21714                vec![Point::zero()..Point::zero()],
21715                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21716            );
21717        })
21718    });
21719}
21720
21721#[gpui::test]
21722async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21723    init_test(cx, |_| {});
21724
21725    let fs = FakeFs::new(cx.executor());
21726    let main_text = r#"fn main() {
21727println!("1");
21728println!("2");
21729println!("3");
21730println!("4");
21731println!("5");
21732}"#;
21733    let lib_text = "mod foo {}";
21734    fs.insert_tree(
21735        path!("/a"),
21736        json!({
21737            "lib.rs": lib_text,
21738            "main.rs": main_text,
21739        }),
21740    )
21741    .await;
21742
21743    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21744    let (workspace, cx) =
21745        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21746    let worktree_id = workspace.update(cx, |workspace, cx| {
21747        workspace.project().update(cx, |project, cx| {
21748            project.worktrees(cx).next().unwrap().read(cx).id()
21749        })
21750    });
21751
21752    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21753    let editor = workspace
21754        .update_in(cx, |workspace, window, cx| {
21755            workspace.open_path(
21756                (worktree_id, "main.rs"),
21757                Some(pane.downgrade()),
21758                true,
21759                window,
21760                cx,
21761            )
21762        })
21763        .unwrap()
21764        .await
21765        .downcast::<Editor>()
21766        .unwrap();
21767    pane.update(cx, |pane, cx| {
21768        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21769        open_editor.update(cx, |editor, cx| {
21770            assert_eq!(
21771                editor.display_text(cx),
21772                main_text,
21773                "Original main.rs text on initial open",
21774            );
21775        })
21776    });
21777    editor.update_in(cx, |editor, window, cx| {
21778        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21779    });
21780
21781    cx.update_global(|store: &mut SettingsStore, cx| {
21782        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21783            s.restore_on_file_reopen = Some(false);
21784        });
21785    });
21786    editor.update_in(cx, |editor, window, cx| {
21787        editor.fold_ranges(
21788            vec![
21789                Point::new(1, 0)..Point::new(1, 1),
21790                Point::new(2, 0)..Point::new(2, 2),
21791                Point::new(3, 0)..Point::new(3, 3),
21792            ],
21793            false,
21794            window,
21795            cx,
21796        );
21797    });
21798    pane.update_in(cx, |pane, window, cx| {
21799        pane.close_all_items(&CloseAllItems::default(), window, cx)
21800    })
21801    .await
21802    .unwrap();
21803    pane.update(cx, |pane, _| {
21804        assert!(pane.active_item().is_none());
21805    });
21806    cx.update_global(|store: &mut SettingsStore, cx| {
21807        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21808            s.restore_on_file_reopen = Some(true);
21809        });
21810    });
21811
21812    let _editor_reopened = workspace
21813        .update_in(cx, |workspace, window, cx| {
21814            workspace.open_path(
21815                (worktree_id, "main.rs"),
21816                Some(pane.downgrade()),
21817                true,
21818                window,
21819                cx,
21820            )
21821        })
21822        .unwrap()
21823        .await
21824        .downcast::<Editor>()
21825        .unwrap();
21826    pane.update(cx, |pane, cx| {
21827        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21828        open_editor.update(cx, |editor, cx| {
21829            assert_eq!(
21830                editor.display_text(cx),
21831                main_text,
21832                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21833            );
21834        })
21835    });
21836}
21837
21838#[gpui::test]
21839async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21840    struct EmptyModalView {
21841        focus_handle: gpui::FocusHandle,
21842    }
21843    impl EventEmitter<DismissEvent> for EmptyModalView {}
21844    impl Render for EmptyModalView {
21845        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21846            div()
21847        }
21848    }
21849    impl Focusable for EmptyModalView {
21850        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21851            self.focus_handle.clone()
21852        }
21853    }
21854    impl workspace::ModalView for EmptyModalView {}
21855    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21856        EmptyModalView {
21857            focus_handle: cx.focus_handle(),
21858        }
21859    }
21860
21861    init_test(cx, |_| {});
21862
21863    let fs = FakeFs::new(cx.executor());
21864    let project = Project::test(fs, [], cx).await;
21865    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21866    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21867    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21868    let editor = cx.new_window_entity(|window, cx| {
21869        Editor::new(
21870            EditorMode::full(),
21871            buffer,
21872            Some(project.clone()),
21873            window,
21874            cx,
21875        )
21876    });
21877    workspace
21878        .update(cx, |workspace, window, cx| {
21879            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21880        })
21881        .unwrap();
21882    editor.update_in(cx, |editor, window, cx| {
21883        editor.open_context_menu(&OpenContextMenu, window, cx);
21884        assert!(editor.mouse_context_menu.is_some());
21885    });
21886    workspace
21887        .update(cx, |workspace, window, cx| {
21888            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21889        })
21890        .unwrap();
21891    cx.read(|cx| {
21892        assert!(editor.read(cx).mouse_context_menu.is_none());
21893    });
21894}
21895
21896#[gpui::test]
21897async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21898    init_test(cx, |_| {});
21899
21900    let fs = FakeFs::new(cx.executor());
21901    fs.insert_file(path!("/file.html"), Default::default())
21902        .await;
21903
21904    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21905
21906    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21907    let html_language = Arc::new(Language::new(
21908        LanguageConfig {
21909            name: "HTML".into(),
21910            matcher: LanguageMatcher {
21911                path_suffixes: vec!["html".to_string()],
21912                ..LanguageMatcher::default()
21913            },
21914            brackets: BracketPairConfig {
21915                pairs: vec![BracketPair {
21916                    start: "<".into(),
21917                    end: ">".into(),
21918                    close: true,
21919                    ..Default::default()
21920                }],
21921                ..Default::default()
21922            },
21923            ..Default::default()
21924        },
21925        Some(tree_sitter_html::LANGUAGE.into()),
21926    ));
21927    language_registry.add(html_language);
21928    let mut fake_servers = language_registry.register_fake_lsp(
21929        "HTML",
21930        FakeLspAdapter {
21931            capabilities: lsp::ServerCapabilities {
21932                completion_provider: Some(lsp::CompletionOptions {
21933                    resolve_provider: Some(true),
21934                    ..Default::default()
21935                }),
21936                ..Default::default()
21937            },
21938            ..Default::default()
21939        },
21940    );
21941
21942    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21943    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21944
21945    let worktree_id = workspace
21946        .update(cx, |workspace, _window, cx| {
21947            workspace.project().update(cx, |project, cx| {
21948                project.worktrees(cx).next().unwrap().read(cx).id()
21949            })
21950        })
21951        .unwrap();
21952    project
21953        .update(cx, |project, cx| {
21954            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21955        })
21956        .await
21957        .unwrap();
21958    let editor = workspace
21959        .update(cx, |workspace, window, cx| {
21960            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21961        })
21962        .unwrap()
21963        .await
21964        .unwrap()
21965        .downcast::<Editor>()
21966        .unwrap();
21967
21968    let fake_server = fake_servers.next().await.unwrap();
21969    editor.update_in(cx, |editor, window, cx| {
21970        editor.set_text("<ad></ad>", window, cx);
21971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21972            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21973        });
21974        let Some((buffer, _)) = editor
21975            .buffer
21976            .read(cx)
21977            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21978        else {
21979            panic!("Failed to get buffer for selection position");
21980        };
21981        let buffer = buffer.read(cx);
21982        let buffer_id = buffer.remote_id();
21983        let opening_range =
21984            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21985        let closing_range =
21986            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21987        let mut linked_ranges = HashMap::default();
21988        linked_ranges.insert(
21989            buffer_id,
21990            vec![(opening_range.clone(), vec![closing_range.clone()])],
21991        );
21992        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21993    });
21994    let mut completion_handle =
21995        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21996            Ok(Some(lsp::CompletionResponse::Array(vec![
21997                lsp::CompletionItem {
21998                    label: "head".to_string(),
21999                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22000                        lsp::InsertReplaceEdit {
22001                            new_text: "head".to_string(),
22002                            insert: lsp::Range::new(
22003                                lsp::Position::new(0, 1),
22004                                lsp::Position::new(0, 3),
22005                            ),
22006                            replace: lsp::Range::new(
22007                                lsp::Position::new(0, 1),
22008                                lsp::Position::new(0, 3),
22009                            ),
22010                        },
22011                    )),
22012                    ..Default::default()
22013                },
22014            ])))
22015        });
22016    editor.update_in(cx, |editor, window, cx| {
22017        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22018    });
22019    cx.run_until_parked();
22020    completion_handle.next().await.unwrap();
22021    editor.update(cx, |editor, _| {
22022        assert!(
22023            editor.context_menu_visible(),
22024            "Completion menu should be visible"
22025        );
22026    });
22027    editor.update_in(cx, |editor, window, cx| {
22028        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22029    });
22030    cx.executor().run_until_parked();
22031    editor.update(cx, |editor, cx| {
22032        assert_eq!(editor.text(cx), "<head></head>");
22033    });
22034}
22035
22036#[gpui::test]
22037async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22038    init_test(cx, |_| {});
22039
22040    let fs = FakeFs::new(cx.executor());
22041    fs.insert_tree(
22042        path!("/root"),
22043        json!({
22044            "a": {
22045                "main.rs": "fn main() {}",
22046            },
22047            "foo": {
22048                "bar": {
22049                    "external_file.rs": "pub mod external {}",
22050                }
22051            }
22052        }),
22053    )
22054    .await;
22055
22056    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22057    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22058    language_registry.add(rust_lang());
22059    let _fake_servers = language_registry.register_fake_lsp(
22060        "Rust",
22061        FakeLspAdapter {
22062            ..FakeLspAdapter::default()
22063        },
22064    );
22065    let (workspace, cx) =
22066        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22067    let worktree_id = workspace.update(cx, |workspace, cx| {
22068        workspace.project().update(cx, |project, cx| {
22069            project.worktrees(cx).next().unwrap().read(cx).id()
22070        })
22071    });
22072
22073    let assert_language_servers_count =
22074        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22075            project.update(cx, |project, cx| {
22076                let current = project
22077                    .lsp_store()
22078                    .read(cx)
22079                    .as_local()
22080                    .unwrap()
22081                    .language_servers
22082                    .len();
22083                assert_eq!(expected, current, "{context}");
22084            });
22085        };
22086
22087    assert_language_servers_count(
22088        0,
22089        "No servers should be running before any file is open",
22090        cx,
22091    );
22092    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22093    let main_editor = workspace
22094        .update_in(cx, |workspace, window, cx| {
22095            workspace.open_path(
22096                (worktree_id, "main.rs"),
22097                Some(pane.downgrade()),
22098                true,
22099                window,
22100                cx,
22101            )
22102        })
22103        .unwrap()
22104        .await
22105        .downcast::<Editor>()
22106        .unwrap();
22107    pane.update(cx, |pane, cx| {
22108        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22109        open_editor.update(cx, |editor, cx| {
22110            assert_eq!(
22111                editor.display_text(cx),
22112                "fn main() {}",
22113                "Original main.rs text on initial open",
22114            );
22115        });
22116        assert_eq!(open_editor, main_editor);
22117    });
22118    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22119
22120    let external_editor = workspace
22121        .update_in(cx, |workspace, window, cx| {
22122            workspace.open_abs_path(
22123                PathBuf::from("/root/foo/bar/external_file.rs"),
22124                OpenOptions::default(),
22125                window,
22126                cx,
22127            )
22128        })
22129        .await
22130        .expect("opening external file")
22131        .downcast::<Editor>()
22132        .expect("downcasted external file's open element to editor");
22133    pane.update(cx, |pane, cx| {
22134        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22135        open_editor.update(cx, |editor, cx| {
22136            assert_eq!(
22137                editor.display_text(cx),
22138                "pub mod external {}",
22139                "External file is open now",
22140            );
22141        });
22142        assert_eq!(open_editor, external_editor);
22143    });
22144    assert_language_servers_count(
22145        1,
22146        "Second, external, *.rs file should join the existing server",
22147        cx,
22148    );
22149
22150    pane.update_in(cx, |pane, window, cx| {
22151        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22152    })
22153    .await
22154    .unwrap();
22155    pane.update_in(cx, |pane, window, cx| {
22156        pane.navigate_backward(window, cx);
22157    });
22158    cx.run_until_parked();
22159    pane.update(cx, |pane, cx| {
22160        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22161        open_editor.update(cx, |editor, cx| {
22162            assert_eq!(
22163                editor.display_text(cx),
22164                "pub mod external {}",
22165                "External file is open now",
22166            );
22167        });
22168    });
22169    assert_language_servers_count(
22170        1,
22171        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22172        cx,
22173    );
22174
22175    cx.update(|_, cx| {
22176        workspace::reload(&workspace::Reload::default(), cx);
22177    });
22178    assert_language_servers_count(
22179        1,
22180        "After reloading the worktree with local and external files opened, only one project should be started",
22181        cx,
22182    );
22183}
22184
22185#[gpui::test]
22186async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22187    init_test(cx, |_| {});
22188
22189    let mut cx = EditorTestContext::new(cx).await;
22190    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22191    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22192
22193    // test cursor move to start of each line on tab
22194    // for `if`, `elif`, `else`, `while`, `with` and `for`
22195    cx.set_state(indoc! {"
22196        def main():
22197        ˇ    for item in items:
22198        ˇ        while item.active:
22199        ˇ            if item.value > 10:
22200        ˇ                continue
22201        ˇ            elif item.value < 0:
22202        ˇ                break
22203        ˇ            else:
22204        ˇ                with item.context() as ctx:
22205        ˇ                    yield count
22206        ˇ        else:
22207        ˇ            log('while else')
22208        ˇ    else:
22209        ˇ        log('for else')
22210    "});
22211    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22212    cx.assert_editor_state(indoc! {"
22213        def main():
22214            ˇfor item in items:
22215                ˇwhile item.active:
22216                    ˇif item.value > 10:
22217                        ˇcontinue
22218                    ˇelif item.value < 0:
22219                        ˇbreak
22220                    ˇelse:
22221                        ˇwith item.context() as ctx:
22222                            ˇyield count
22223                ˇelse:
22224                    ˇlog('while else')
22225            ˇelse:
22226                ˇlog('for else')
22227    "});
22228    // test relative indent is preserved when tab
22229    // for `if`, `elif`, `else`, `while`, `with` and `for`
22230    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22231    cx.assert_editor_state(indoc! {"
22232        def main():
22233                ˇfor item in items:
22234                    ˇwhile item.active:
22235                        ˇif item.value > 10:
22236                            ˇcontinue
22237                        ˇelif item.value < 0:
22238                            ˇbreak
22239                        ˇelse:
22240                            ˇwith item.context() as ctx:
22241                                ˇyield count
22242                    ˇelse:
22243                        ˇlog('while else')
22244                ˇelse:
22245                    ˇlog('for else')
22246    "});
22247
22248    // test cursor move to start of each line on tab
22249    // for `try`, `except`, `else`, `finally`, `match` and `def`
22250    cx.set_state(indoc! {"
22251        def main():
22252        ˇ    try:
22253        ˇ        fetch()
22254        ˇ    except ValueError:
22255        ˇ        handle_error()
22256        ˇ    else:
22257        ˇ        match value:
22258        ˇ            case _:
22259        ˇ    finally:
22260        ˇ        def status():
22261        ˇ            return 0
22262    "});
22263    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22264    cx.assert_editor_state(indoc! {"
22265        def main():
22266            ˇtry:
22267                ˇfetch()
22268            ˇexcept ValueError:
22269                ˇhandle_error()
22270            ˇelse:
22271                ˇmatch value:
22272                    ˇcase _:
22273            ˇfinally:
22274                ˇdef status():
22275                    ˇreturn 0
22276    "});
22277    // test relative indent is preserved when tab
22278    // for `try`, `except`, `else`, `finally`, `match` and `def`
22279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22280    cx.assert_editor_state(indoc! {"
22281        def main():
22282                ˇtry:
22283                    ˇfetch()
22284                ˇexcept ValueError:
22285                    ˇhandle_error()
22286                ˇelse:
22287                    ˇmatch value:
22288                        ˇcase _:
22289                ˇfinally:
22290                    ˇdef status():
22291                        ˇreturn 0
22292    "});
22293}
22294
22295#[gpui::test]
22296async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22297    init_test(cx, |_| {});
22298
22299    let mut cx = EditorTestContext::new(cx).await;
22300    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22301    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22302
22303    // test `else` auto outdents when typed inside `if` block
22304    cx.set_state(indoc! {"
22305        def main():
22306            if i == 2:
22307                return
22308                ˇ
22309    "});
22310    cx.update_editor(|editor, window, cx| {
22311        editor.handle_input("else:", window, cx);
22312    });
22313    cx.assert_editor_state(indoc! {"
22314        def main():
22315            if i == 2:
22316                return
22317            else:ˇ
22318    "});
22319
22320    // test `except` auto outdents when typed inside `try` block
22321    cx.set_state(indoc! {"
22322        def main():
22323            try:
22324                i = 2
22325                ˇ
22326    "});
22327    cx.update_editor(|editor, window, cx| {
22328        editor.handle_input("except:", window, cx);
22329    });
22330    cx.assert_editor_state(indoc! {"
22331        def main():
22332            try:
22333                i = 2
22334            except:ˇ
22335    "});
22336
22337    // test `else` auto outdents when typed inside `except` block
22338    cx.set_state(indoc! {"
22339        def main():
22340            try:
22341                i = 2
22342            except:
22343                j = 2
22344                ˇ
22345    "});
22346    cx.update_editor(|editor, window, cx| {
22347        editor.handle_input("else:", window, cx);
22348    });
22349    cx.assert_editor_state(indoc! {"
22350        def main():
22351            try:
22352                i = 2
22353            except:
22354                j = 2
22355            else:ˇ
22356    "});
22357
22358    // test `finally` auto outdents when typed inside `else` block
22359    cx.set_state(indoc! {"
22360        def main():
22361            try:
22362                i = 2
22363            except:
22364                j = 2
22365            else:
22366                k = 2
22367                ˇ
22368    "});
22369    cx.update_editor(|editor, window, cx| {
22370        editor.handle_input("finally:", window, cx);
22371    });
22372    cx.assert_editor_state(indoc! {"
22373        def main():
22374            try:
22375                i = 2
22376            except:
22377                j = 2
22378            else:
22379                k = 2
22380            finally:ˇ
22381    "});
22382
22383    // test `else` does not outdents when typed inside `except` block right after for block
22384    cx.set_state(indoc! {"
22385        def main():
22386            try:
22387                i = 2
22388            except:
22389                for i in range(n):
22390                    pass
22391                ˇ
22392    "});
22393    cx.update_editor(|editor, window, cx| {
22394        editor.handle_input("else:", window, cx);
22395    });
22396    cx.assert_editor_state(indoc! {"
22397        def main():
22398            try:
22399                i = 2
22400            except:
22401                for i in range(n):
22402                    pass
22403                else:ˇ
22404    "});
22405
22406    // test `finally` auto outdents when typed inside `else` block right after for block
22407    cx.set_state(indoc! {"
22408        def main():
22409            try:
22410                i = 2
22411            except:
22412                j = 2
22413            else:
22414                for i in range(n):
22415                    pass
22416                ˇ
22417    "});
22418    cx.update_editor(|editor, window, cx| {
22419        editor.handle_input("finally:", window, cx);
22420    });
22421    cx.assert_editor_state(indoc! {"
22422        def main():
22423            try:
22424                i = 2
22425            except:
22426                j = 2
22427            else:
22428                for i in range(n):
22429                    pass
22430            finally:ˇ
22431    "});
22432
22433    // test `except` outdents to inner "try" block
22434    cx.set_state(indoc! {"
22435        def main():
22436            try:
22437                i = 2
22438                if i == 2:
22439                    try:
22440                        i = 3
22441                        ˇ
22442    "});
22443    cx.update_editor(|editor, window, cx| {
22444        editor.handle_input("except:", window, cx);
22445    });
22446    cx.assert_editor_state(indoc! {"
22447        def main():
22448            try:
22449                i = 2
22450                if i == 2:
22451                    try:
22452                        i = 3
22453                    except:ˇ
22454    "});
22455
22456    // test `except` outdents to outer "try" block
22457    cx.set_state(indoc! {"
22458        def main():
22459            try:
22460                i = 2
22461                if i == 2:
22462                    try:
22463                        i = 3
22464                ˇ
22465    "});
22466    cx.update_editor(|editor, window, cx| {
22467        editor.handle_input("except:", window, cx);
22468    });
22469    cx.assert_editor_state(indoc! {"
22470        def main():
22471            try:
22472                i = 2
22473                if i == 2:
22474                    try:
22475                        i = 3
22476            except:ˇ
22477    "});
22478
22479    // test `else` stays at correct indent when typed after `for` block
22480    cx.set_state(indoc! {"
22481        def main():
22482            for i in range(10):
22483                if i == 3:
22484                    break
22485            ˇ
22486    "});
22487    cx.update_editor(|editor, window, cx| {
22488        editor.handle_input("else:", window, cx);
22489    });
22490    cx.assert_editor_state(indoc! {"
22491        def main():
22492            for i in range(10):
22493                if i == 3:
22494                    break
22495            else:ˇ
22496    "});
22497
22498    // test does not outdent on typing after line with square brackets
22499    cx.set_state(indoc! {"
22500        def f() -> list[str]:
22501            ˇ
22502    "});
22503    cx.update_editor(|editor, window, cx| {
22504        editor.handle_input("a", window, cx);
22505    });
22506    cx.assert_editor_state(indoc! {"
22507        def f() -> list[str]:
2250822509    "});
22510
22511    // test does not outdent on typing : after case keyword
22512    cx.set_state(indoc! {"
22513        match 1:
22514            caseˇ
22515    "});
22516    cx.update_editor(|editor, window, cx| {
22517        editor.handle_input(":", window, cx);
22518    });
22519    cx.assert_editor_state(indoc! {"
22520        match 1:
22521            case:ˇ
22522    "});
22523}
22524
22525#[gpui::test]
22526async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22527    init_test(cx, |_| {});
22528    update_test_language_settings(cx, |settings| {
22529        settings.defaults.extend_comment_on_newline = Some(false);
22530    });
22531    let mut cx = EditorTestContext::new(cx).await;
22532    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22533    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22534
22535    // test correct indent after newline on comment
22536    cx.set_state(indoc! {"
22537        # COMMENT:ˇ
22538    "});
22539    cx.update_editor(|editor, window, cx| {
22540        editor.newline(&Newline, window, cx);
22541    });
22542    cx.assert_editor_state(indoc! {"
22543        # COMMENT:
22544        ˇ
22545    "});
22546
22547    // test correct indent after newline in brackets
22548    cx.set_state(indoc! {"
22549        {ˇ}
22550    "});
22551    cx.update_editor(|editor, window, cx| {
22552        editor.newline(&Newline, window, cx);
22553    });
22554    cx.run_until_parked();
22555    cx.assert_editor_state(indoc! {"
22556        {
22557            ˇ
22558        }
22559    "});
22560
22561    cx.set_state(indoc! {"
22562        (ˇ)
22563    "});
22564    cx.update_editor(|editor, window, cx| {
22565        editor.newline(&Newline, window, cx);
22566    });
22567    cx.run_until_parked();
22568    cx.assert_editor_state(indoc! {"
22569        (
22570            ˇ
22571        )
22572    "});
22573
22574    // do not indent after empty lists or dictionaries
22575    cx.set_state(indoc! {"
22576        a = []ˇ
22577    "});
22578    cx.update_editor(|editor, window, cx| {
22579        editor.newline(&Newline, window, cx);
22580    });
22581    cx.run_until_parked();
22582    cx.assert_editor_state(indoc! {"
22583        a = []
22584        ˇ
22585    "});
22586}
22587
22588#[gpui::test]
22589async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22590    init_test(cx, |_| {});
22591
22592    let mut cx = EditorTestContext::new(cx).await;
22593    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22594    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22595
22596    // test cursor move to start of each line on tab
22597    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22598    cx.set_state(indoc! {"
22599        function main() {
22600        ˇ    for item in $items; do
22601        ˇ        while [ -n \"$item\" ]; do
22602        ˇ            if [ \"$value\" -gt 10 ]; then
22603        ˇ                continue
22604        ˇ            elif [ \"$value\" -lt 0 ]; then
22605        ˇ                break
22606        ˇ            else
22607        ˇ                echo \"$item\"
22608        ˇ            fi
22609        ˇ        done
22610        ˇ    done
22611        ˇ}
22612    "});
22613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22614    cx.assert_editor_state(indoc! {"
22615        function main() {
22616            ˇfor item in $items; do
22617                ˇwhile [ -n \"$item\" ]; do
22618                    ˇif [ \"$value\" -gt 10 ]; then
22619                        ˇcontinue
22620                    ˇelif [ \"$value\" -lt 0 ]; then
22621                        ˇbreak
22622                    ˇelse
22623                        ˇecho \"$item\"
22624                    ˇfi
22625                ˇdone
22626            ˇdone
22627        ˇ}
22628    "});
22629    // test relative indent is preserved when tab
22630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22631    cx.assert_editor_state(indoc! {"
22632        function main() {
22633                ˇfor item in $items; do
22634                    ˇwhile [ -n \"$item\" ]; do
22635                        ˇif [ \"$value\" -gt 10 ]; then
22636                            ˇcontinue
22637                        ˇelif [ \"$value\" -lt 0 ]; then
22638                            ˇbreak
22639                        ˇelse
22640                            ˇecho \"$item\"
22641                        ˇfi
22642                    ˇdone
22643                ˇdone
22644            ˇ}
22645    "});
22646
22647    // test cursor move to start of each line on tab
22648    // for `case` statement with patterns
22649    cx.set_state(indoc! {"
22650        function handle() {
22651        ˇ    case \"$1\" in
22652        ˇ        start)
22653        ˇ            echo \"a\"
22654        ˇ            ;;
22655        ˇ        stop)
22656        ˇ            echo \"b\"
22657        ˇ            ;;
22658        ˇ        *)
22659        ˇ            echo \"c\"
22660        ˇ            ;;
22661        ˇ    esac
22662        ˇ}
22663    "});
22664    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22665    cx.assert_editor_state(indoc! {"
22666        function handle() {
22667            ˇcase \"$1\" in
22668                ˇstart)
22669                    ˇecho \"a\"
22670                    ˇ;;
22671                ˇstop)
22672                    ˇecho \"b\"
22673                    ˇ;;
22674                ˇ*)
22675                    ˇecho \"c\"
22676                    ˇ;;
22677            ˇesac
22678        ˇ}
22679    "});
22680}
22681
22682#[gpui::test]
22683async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
22684    init_test(cx, |_| {});
22685
22686    let mut cx = EditorTestContext::new(cx).await;
22687    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22688    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22689
22690    // test indents on comment insert
22691    cx.set_state(indoc! {"
22692        function main() {
22693        ˇ    for item in $items; do
22694        ˇ        while [ -n \"$item\" ]; do
22695        ˇ            if [ \"$value\" -gt 10 ]; then
22696        ˇ                continue
22697        ˇ            elif [ \"$value\" -lt 0 ]; then
22698        ˇ                break
22699        ˇ            else
22700        ˇ                echo \"$item\"
22701        ˇ            fi
22702        ˇ        done
22703        ˇ    done
22704        ˇ}
22705    "});
22706    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
22707    cx.assert_editor_state(indoc! {"
22708        function main() {
22709        #ˇ    for item in $items; do
22710        #ˇ        while [ -n \"$item\" ]; do
22711        #ˇ            if [ \"$value\" -gt 10 ]; then
22712        #ˇ                continue
22713        #ˇ            elif [ \"$value\" -lt 0 ]; then
22714        #ˇ                break
22715        #ˇ            else
22716        #ˇ                echo \"$item\"
22717        #ˇ            fi
22718        #ˇ        done
22719        #ˇ    done
22720        #ˇ}
22721    "});
22722}
22723
22724#[gpui::test]
22725async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
22726    init_test(cx, |_| {});
22727
22728    let mut cx = EditorTestContext::new(cx).await;
22729    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22730    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22731
22732    // test `else` auto outdents when typed inside `if` block
22733    cx.set_state(indoc! {"
22734        if [ \"$1\" = \"test\" ]; then
22735            echo \"foo bar\"
22736            ˇ
22737    "});
22738    cx.update_editor(|editor, window, cx| {
22739        editor.handle_input("else", window, cx);
22740    });
22741    cx.assert_editor_state(indoc! {"
22742        if [ \"$1\" = \"test\" ]; then
22743            echo \"foo bar\"
22744        elseˇ
22745    "});
22746
22747    // test `elif` auto outdents when typed inside `if` block
22748    cx.set_state(indoc! {"
22749        if [ \"$1\" = \"test\" ]; then
22750            echo \"foo bar\"
22751            ˇ
22752    "});
22753    cx.update_editor(|editor, window, cx| {
22754        editor.handle_input("elif", window, cx);
22755    });
22756    cx.assert_editor_state(indoc! {"
22757        if [ \"$1\" = \"test\" ]; then
22758            echo \"foo bar\"
22759        elifˇ
22760    "});
22761
22762    // test `fi` auto outdents when typed inside `else` block
22763    cx.set_state(indoc! {"
22764        if [ \"$1\" = \"test\" ]; then
22765            echo \"foo bar\"
22766        else
22767            echo \"bar baz\"
22768            ˇ
22769    "});
22770    cx.update_editor(|editor, window, cx| {
22771        editor.handle_input("fi", window, cx);
22772    });
22773    cx.assert_editor_state(indoc! {"
22774        if [ \"$1\" = \"test\" ]; then
22775            echo \"foo bar\"
22776        else
22777            echo \"bar baz\"
22778        fiˇ
22779    "});
22780
22781    // test `done` auto outdents when typed inside `while` block
22782    cx.set_state(indoc! {"
22783        while read line; do
22784            echo \"$line\"
22785            ˇ
22786    "});
22787    cx.update_editor(|editor, window, cx| {
22788        editor.handle_input("done", window, cx);
22789    });
22790    cx.assert_editor_state(indoc! {"
22791        while read line; do
22792            echo \"$line\"
22793        doneˇ
22794    "});
22795
22796    // test `done` auto outdents when typed inside `for` block
22797    cx.set_state(indoc! {"
22798        for file in *.txt; do
22799            cat \"$file\"
22800            ˇ
22801    "});
22802    cx.update_editor(|editor, window, cx| {
22803        editor.handle_input("done", window, cx);
22804    });
22805    cx.assert_editor_state(indoc! {"
22806        for file in *.txt; do
22807            cat \"$file\"
22808        doneˇ
22809    "});
22810
22811    // test `esac` auto outdents when typed inside `case` block
22812    cx.set_state(indoc! {"
22813        case \"$1\" in
22814            start)
22815                echo \"foo bar\"
22816                ;;
22817            stop)
22818                echo \"bar baz\"
22819                ;;
22820            ˇ
22821    "});
22822    cx.update_editor(|editor, window, cx| {
22823        editor.handle_input("esac", window, cx);
22824    });
22825    cx.assert_editor_state(indoc! {"
22826        case \"$1\" in
22827            start)
22828                echo \"foo bar\"
22829                ;;
22830            stop)
22831                echo \"bar baz\"
22832                ;;
22833        esacˇ
22834    "});
22835
22836    // test `*)` auto outdents when typed inside `case` block
22837    cx.set_state(indoc! {"
22838        case \"$1\" in
22839            start)
22840                echo \"foo bar\"
22841                ;;
22842                ˇ
22843    "});
22844    cx.update_editor(|editor, window, cx| {
22845        editor.handle_input("*)", window, cx);
22846    });
22847    cx.assert_editor_state(indoc! {"
22848        case \"$1\" in
22849            start)
22850                echo \"foo bar\"
22851                ;;
22852            *)ˇ
22853    "});
22854
22855    // test `fi` outdents to correct level with nested if blocks
22856    cx.set_state(indoc! {"
22857        if [ \"$1\" = \"test\" ]; then
22858            echo \"outer if\"
22859            if [ \"$2\" = \"debug\" ]; then
22860                echo \"inner if\"
22861                ˇ
22862    "});
22863    cx.update_editor(|editor, window, cx| {
22864        editor.handle_input("fi", window, cx);
22865    });
22866    cx.assert_editor_state(indoc! {"
22867        if [ \"$1\" = \"test\" ]; then
22868            echo \"outer if\"
22869            if [ \"$2\" = \"debug\" ]; then
22870                echo \"inner if\"
22871            fiˇ
22872    "});
22873}
22874
22875#[gpui::test]
22876async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
22877    init_test(cx, |_| {});
22878    update_test_language_settings(cx, |settings| {
22879        settings.defaults.extend_comment_on_newline = Some(false);
22880    });
22881    let mut cx = EditorTestContext::new(cx).await;
22882    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22883    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22884
22885    // test correct indent after newline on comment
22886    cx.set_state(indoc! {"
22887        # COMMENT:ˇ
22888    "});
22889    cx.update_editor(|editor, window, cx| {
22890        editor.newline(&Newline, window, cx);
22891    });
22892    cx.assert_editor_state(indoc! {"
22893        # COMMENT:
22894        ˇ
22895    "});
22896
22897    // test correct indent after newline after `then`
22898    cx.set_state(indoc! {"
22899
22900        if [ \"$1\" = \"test\" ]; thenˇ
22901    "});
22902    cx.update_editor(|editor, window, cx| {
22903        editor.newline(&Newline, window, cx);
22904    });
22905    cx.run_until_parked();
22906    cx.assert_editor_state(indoc! {"
22907
22908        if [ \"$1\" = \"test\" ]; then
22909            ˇ
22910    "});
22911
22912    // test correct indent after newline after `else`
22913    cx.set_state(indoc! {"
22914        if [ \"$1\" = \"test\" ]; then
22915        elseˇ
22916    "});
22917    cx.update_editor(|editor, window, cx| {
22918        editor.newline(&Newline, window, cx);
22919    });
22920    cx.run_until_parked();
22921    cx.assert_editor_state(indoc! {"
22922        if [ \"$1\" = \"test\" ]; then
22923        else
22924            ˇ
22925    "});
22926
22927    // test correct indent after newline after `elif`
22928    cx.set_state(indoc! {"
22929        if [ \"$1\" = \"test\" ]; then
22930        elifˇ
22931    "});
22932    cx.update_editor(|editor, window, cx| {
22933        editor.newline(&Newline, window, cx);
22934    });
22935    cx.run_until_parked();
22936    cx.assert_editor_state(indoc! {"
22937        if [ \"$1\" = \"test\" ]; then
22938        elif
22939            ˇ
22940    "});
22941
22942    // test correct indent after newline after `do`
22943    cx.set_state(indoc! {"
22944        for file in *.txt; doˇ
22945    "});
22946    cx.update_editor(|editor, window, cx| {
22947        editor.newline(&Newline, window, cx);
22948    });
22949    cx.run_until_parked();
22950    cx.assert_editor_state(indoc! {"
22951        for file in *.txt; do
22952            ˇ
22953    "});
22954
22955    // test correct indent after newline after case pattern
22956    cx.set_state(indoc! {"
22957        case \"$1\" in
22958            start)ˇ
22959    "});
22960    cx.update_editor(|editor, window, cx| {
22961        editor.newline(&Newline, window, cx);
22962    });
22963    cx.run_until_parked();
22964    cx.assert_editor_state(indoc! {"
22965        case \"$1\" in
22966            start)
22967                ˇ
22968    "});
22969
22970    // test correct indent after newline after case pattern
22971    cx.set_state(indoc! {"
22972        case \"$1\" in
22973            start)
22974                ;;
22975            *)ˇ
22976    "});
22977    cx.update_editor(|editor, window, cx| {
22978        editor.newline(&Newline, window, cx);
22979    });
22980    cx.run_until_parked();
22981    cx.assert_editor_state(indoc! {"
22982        case \"$1\" in
22983            start)
22984                ;;
22985            *)
22986                ˇ
22987    "});
22988
22989    // test correct indent after newline after function opening brace
22990    cx.set_state(indoc! {"
22991        function test() {ˇ}
22992    "});
22993    cx.update_editor(|editor, window, cx| {
22994        editor.newline(&Newline, window, cx);
22995    });
22996    cx.run_until_parked();
22997    cx.assert_editor_state(indoc! {"
22998        function test() {
22999            ˇ
23000        }
23001    "});
23002
23003    // test no extra indent after semicolon on same line
23004    cx.set_state(indoc! {"
23005        echo \"test\"23006    "});
23007    cx.update_editor(|editor, window, cx| {
23008        editor.newline(&Newline, window, cx);
23009    });
23010    cx.run_until_parked();
23011    cx.assert_editor_state(indoc! {"
23012        echo \"test\";
23013        ˇ
23014    "});
23015}
23016
23017fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23018    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23019    point..point
23020}
23021
23022#[track_caller]
23023fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23024    let (text, ranges) = marked_text_ranges(marked_text, true);
23025    assert_eq!(editor.text(cx), text);
23026    assert_eq!(
23027        editor.selections.ranges(cx),
23028        ranges,
23029        "Assert selections are {}",
23030        marked_text
23031    );
23032}
23033
23034pub fn handle_signature_help_request(
23035    cx: &mut EditorLspTestContext,
23036    mocked_response: lsp::SignatureHelp,
23037) -> impl Future<Output = ()> + use<> {
23038    let mut request =
23039        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23040            let mocked_response = mocked_response.clone();
23041            async move { Ok(Some(mocked_response)) }
23042        });
23043
23044    async move {
23045        request.next().await;
23046    }
23047}
23048
23049#[track_caller]
23050pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23051    cx.update_editor(|editor, _, _| {
23052        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23053            let entries = menu.entries.borrow();
23054            let entries = entries
23055                .iter()
23056                .map(|entry| entry.string.as_str())
23057                .collect::<Vec<_>>();
23058            assert_eq!(entries, expected);
23059        } else {
23060            panic!("Expected completions menu");
23061        }
23062    });
23063}
23064
23065/// Handle completion request passing a marked string specifying where the completion
23066/// should be triggered from using '|' character, what range should be replaced, and what completions
23067/// should be returned using '<' and '>' to delimit the range.
23068///
23069/// Also see `handle_completion_request_with_insert_and_replace`.
23070#[track_caller]
23071pub fn handle_completion_request(
23072    marked_string: &str,
23073    completions: Vec<&'static str>,
23074    is_incomplete: bool,
23075    counter: Arc<AtomicUsize>,
23076    cx: &mut EditorLspTestContext,
23077) -> impl Future<Output = ()> {
23078    let complete_from_marker: TextRangeMarker = '|'.into();
23079    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23080    let (_, mut marked_ranges) = marked_text_ranges_by(
23081        marked_string,
23082        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23083    );
23084
23085    let complete_from_position =
23086        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23087    let replace_range =
23088        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23089
23090    let mut request =
23091        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23092            let completions = completions.clone();
23093            counter.fetch_add(1, atomic::Ordering::Release);
23094            async move {
23095                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23096                assert_eq!(
23097                    params.text_document_position.position,
23098                    complete_from_position
23099                );
23100                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23101                    is_incomplete: is_incomplete,
23102                    item_defaults: None,
23103                    items: completions
23104                        .iter()
23105                        .map(|completion_text| lsp::CompletionItem {
23106                            label: completion_text.to_string(),
23107                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23108                                range: replace_range,
23109                                new_text: completion_text.to_string(),
23110                            })),
23111                            ..Default::default()
23112                        })
23113                        .collect(),
23114                })))
23115            }
23116        });
23117
23118    async move {
23119        request.next().await;
23120    }
23121}
23122
23123/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23124/// given instead, which also contains an `insert` range.
23125///
23126/// This function uses markers to define ranges:
23127/// - `|` marks the cursor position
23128/// - `<>` marks the replace range
23129/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23130pub fn handle_completion_request_with_insert_and_replace(
23131    cx: &mut EditorLspTestContext,
23132    marked_string: &str,
23133    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23134    counter: Arc<AtomicUsize>,
23135) -> impl Future<Output = ()> {
23136    let complete_from_marker: TextRangeMarker = '|'.into();
23137    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23138    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23139
23140    let (_, mut marked_ranges) = marked_text_ranges_by(
23141        marked_string,
23142        vec![
23143            complete_from_marker.clone(),
23144            replace_range_marker.clone(),
23145            insert_range_marker.clone(),
23146        ],
23147    );
23148
23149    let complete_from_position =
23150        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23151    let replace_range =
23152        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23153
23154    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23155        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23156        _ => lsp::Range {
23157            start: replace_range.start,
23158            end: complete_from_position,
23159        },
23160    };
23161
23162    let mut request =
23163        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23164            let completions = completions.clone();
23165            counter.fetch_add(1, atomic::Ordering::Release);
23166            async move {
23167                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23168                assert_eq!(
23169                    params.text_document_position.position, complete_from_position,
23170                    "marker `|` position doesn't match",
23171                );
23172                Ok(Some(lsp::CompletionResponse::Array(
23173                    completions
23174                        .iter()
23175                        .map(|(label, new_text)| lsp::CompletionItem {
23176                            label: label.to_string(),
23177                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23178                                lsp::InsertReplaceEdit {
23179                                    insert: insert_range,
23180                                    replace: replace_range,
23181                                    new_text: new_text.to_string(),
23182                                },
23183                            )),
23184                            ..Default::default()
23185                        })
23186                        .collect(),
23187                )))
23188            }
23189        });
23190
23191    async move {
23192        request.next().await;
23193    }
23194}
23195
23196fn handle_resolve_completion_request(
23197    cx: &mut EditorLspTestContext,
23198    edits: Option<Vec<(&'static str, &'static str)>>,
23199) -> impl Future<Output = ()> {
23200    let edits = edits.map(|edits| {
23201        edits
23202            .iter()
23203            .map(|(marked_string, new_text)| {
23204                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23205                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23206                lsp::TextEdit::new(replace_range, new_text.to_string())
23207            })
23208            .collect::<Vec<_>>()
23209    });
23210
23211    let mut request =
23212        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23213            let edits = edits.clone();
23214            async move {
23215                Ok(lsp::CompletionItem {
23216                    additional_text_edits: edits,
23217                    ..Default::default()
23218                })
23219            }
23220        });
23221
23222    async move {
23223        request.next().await;
23224    }
23225}
23226
23227pub(crate) fn update_test_language_settings(
23228    cx: &mut TestAppContext,
23229    f: impl Fn(&mut AllLanguageSettingsContent),
23230) {
23231    cx.update(|cx| {
23232        SettingsStore::update_global(cx, |store, cx| {
23233            store.update_user_settings::<AllLanguageSettings>(cx, f);
23234        });
23235    });
23236}
23237
23238pub(crate) fn update_test_project_settings(
23239    cx: &mut TestAppContext,
23240    f: impl Fn(&mut ProjectSettings),
23241) {
23242    cx.update(|cx| {
23243        SettingsStore::update_global(cx, |store, cx| {
23244            store.update_user_settings::<ProjectSettings>(cx, f);
23245        });
23246    });
23247}
23248
23249pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23250    cx.update(|cx| {
23251        assets::Assets.load_test_fonts(cx);
23252        let store = SettingsStore::test(cx);
23253        cx.set_global(store);
23254        theme::init(theme::LoadThemes::JustBase, cx);
23255        release_channel::init(SemanticVersion::default(), cx);
23256        client::init_settings(cx);
23257        language::init(cx);
23258        Project::init_settings(cx);
23259        workspace::init_settings(cx);
23260        crate::init(cx);
23261    });
23262    zlog::init_test();
23263    update_test_language_settings(cx, f);
23264}
23265
23266#[track_caller]
23267fn assert_hunk_revert(
23268    not_reverted_text_with_selections: &str,
23269    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23270    expected_reverted_text_with_selections: &str,
23271    base_text: &str,
23272    cx: &mut EditorLspTestContext,
23273) {
23274    cx.set_state(not_reverted_text_with_selections);
23275    cx.set_head_text(base_text);
23276    cx.executor().run_until_parked();
23277
23278    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23279        let snapshot = editor.snapshot(window, cx);
23280        let reverted_hunk_statuses = snapshot
23281            .buffer_snapshot
23282            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23283            .map(|hunk| hunk.status().kind)
23284            .collect::<Vec<_>>();
23285
23286        editor.git_restore(&Default::default(), window, cx);
23287        reverted_hunk_statuses
23288    });
23289    cx.executor().run_until_parked();
23290    cx.assert_editor_state(expected_reverted_text_with_selections);
23291    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23292}
23293
23294#[gpui::test(iterations = 10)]
23295async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23296    init_test(cx, |_| {});
23297
23298    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23299    let counter = diagnostic_requests.clone();
23300
23301    let fs = FakeFs::new(cx.executor());
23302    fs.insert_tree(
23303        path!("/a"),
23304        json!({
23305            "first.rs": "fn main() { let a = 5; }",
23306            "second.rs": "// Test file",
23307        }),
23308    )
23309    .await;
23310
23311    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23312    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23313    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23314
23315    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23316    language_registry.add(rust_lang());
23317    let mut fake_servers = language_registry.register_fake_lsp(
23318        "Rust",
23319        FakeLspAdapter {
23320            capabilities: lsp::ServerCapabilities {
23321                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23322                    lsp::DiagnosticOptions {
23323                        identifier: None,
23324                        inter_file_dependencies: true,
23325                        workspace_diagnostics: true,
23326                        work_done_progress_options: Default::default(),
23327                    },
23328                )),
23329                ..Default::default()
23330            },
23331            ..Default::default()
23332        },
23333    );
23334
23335    let editor = workspace
23336        .update(cx, |workspace, window, cx| {
23337            workspace.open_abs_path(
23338                PathBuf::from(path!("/a/first.rs")),
23339                OpenOptions::default(),
23340                window,
23341                cx,
23342            )
23343        })
23344        .unwrap()
23345        .await
23346        .unwrap()
23347        .downcast::<Editor>()
23348        .unwrap();
23349    let fake_server = fake_servers.next().await.unwrap();
23350    let server_id = fake_server.server.server_id();
23351    let mut first_request = fake_server
23352        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23353            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23354            let result_id = Some(new_result_id.to_string());
23355            assert_eq!(
23356                params.text_document.uri,
23357                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23358            );
23359            async move {
23360                Ok(lsp::DocumentDiagnosticReportResult::Report(
23361                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23362                        related_documents: None,
23363                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23364                            items: Vec::new(),
23365                            result_id,
23366                        },
23367                    }),
23368                ))
23369            }
23370        });
23371
23372    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23373        project.update(cx, |project, cx| {
23374            let buffer_id = editor
23375                .read(cx)
23376                .buffer()
23377                .read(cx)
23378                .as_singleton()
23379                .expect("created a singleton buffer")
23380                .read(cx)
23381                .remote_id();
23382            let buffer_result_id = project
23383                .lsp_store()
23384                .read(cx)
23385                .result_id(server_id, buffer_id, cx);
23386            assert_eq!(expected, buffer_result_id);
23387        });
23388    };
23389
23390    ensure_result_id(None, cx);
23391    cx.executor().advance_clock(Duration::from_millis(60));
23392    cx.executor().run_until_parked();
23393    assert_eq!(
23394        diagnostic_requests.load(atomic::Ordering::Acquire),
23395        1,
23396        "Opening file should trigger diagnostic request"
23397    );
23398    first_request
23399        .next()
23400        .await
23401        .expect("should have sent the first diagnostics pull request");
23402    ensure_result_id(Some("1".to_string()), cx);
23403
23404    // Editing should trigger diagnostics
23405    editor.update_in(cx, |editor, window, cx| {
23406        editor.handle_input("2", window, cx)
23407    });
23408    cx.executor().advance_clock(Duration::from_millis(60));
23409    cx.executor().run_until_parked();
23410    assert_eq!(
23411        diagnostic_requests.load(atomic::Ordering::Acquire),
23412        2,
23413        "Editing should trigger diagnostic request"
23414    );
23415    ensure_result_id(Some("2".to_string()), cx);
23416
23417    // Moving cursor should not trigger diagnostic request
23418    editor.update_in(cx, |editor, window, cx| {
23419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23420            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23421        });
23422    });
23423    cx.executor().advance_clock(Duration::from_millis(60));
23424    cx.executor().run_until_parked();
23425    assert_eq!(
23426        diagnostic_requests.load(atomic::Ordering::Acquire),
23427        2,
23428        "Cursor movement should not trigger diagnostic request"
23429    );
23430    ensure_result_id(Some("2".to_string()), cx);
23431    // Multiple rapid edits should be debounced
23432    for _ in 0..5 {
23433        editor.update_in(cx, |editor, window, cx| {
23434            editor.handle_input("x", window, cx)
23435        });
23436    }
23437    cx.executor().advance_clock(Duration::from_millis(60));
23438    cx.executor().run_until_parked();
23439
23440    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23441    assert!(
23442        final_requests <= 4,
23443        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23444    );
23445    ensure_result_id(Some(final_requests.to_string()), cx);
23446}
23447
23448#[gpui::test]
23449async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23450    // Regression test for issue #11671
23451    // Previously, adding a cursor after moving multiple cursors would reset
23452    // the cursor count instead of adding to the existing cursors.
23453    init_test(cx, |_| {});
23454    let mut cx = EditorTestContext::new(cx).await;
23455
23456    // Create a simple buffer with cursor at start
23457    cx.set_state(indoc! {"
23458        ˇaaaa
23459        bbbb
23460        cccc
23461        dddd
23462        eeee
23463        ffff
23464        gggg
23465        hhhh"});
23466
23467    // Add 2 cursors below (so we have 3 total)
23468    cx.update_editor(|editor, window, cx| {
23469        editor.add_selection_below(&Default::default(), window, cx);
23470        editor.add_selection_below(&Default::default(), window, cx);
23471    });
23472
23473    // Verify we have 3 cursors
23474    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23475    assert_eq!(
23476        initial_count, 3,
23477        "Should have 3 cursors after adding 2 below"
23478    );
23479
23480    // Move down one line
23481    cx.update_editor(|editor, window, cx| {
23482        editor.move_down(&MoveDown, window, cx);
23483    });
23484
23485    // Add another cursor below
23486    cx.update_editor(|editor, window, cx| {
23487        editor.add_selection_below(&Default::default(), window, cx);
23488    });
23489
23490    // Should now have 4 cursors (3 original + 1 new)
23491    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23492    assert_eq!(
23493        final_count, 4,
23494        "Should have 4 cursors after moving and adding another"
23495    );
23496}
23497
23498#[gpui::test(iterations = 10)]
23499async fn test_document_colors(cx: &mut TestAppContext) {
23500    let expected_color = Rgba {
23501        r: 0.33,
23502        g: 0.33,
23503        b: 0.33,
23504        a: 0.33,
23505    };
23506
23507    init_test(cx, |_| {});
23508
23509    let fs = FakeFs::new(cx.executor());
23510    fs.insert_tree(
23511        path!("/a"),
23512        json!({
23513            "first.rs": "fn main() { let a = 5; }",
23514        }),
23515    )
23516    .await;
23517
23518    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23519    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23520    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23521
23522    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23523    language_registry.add(rust_lang());
23524    let mut fake_servers = language_registry.register_fake_lsp(
23525        "Rust",
23526        FakeLspAdapter {
23527            capabilities: lsp::ServerCapabilities {
23528                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23529                ..lsp::ServerCapabilities::default()
23530            },
23531            name: "rust-analyzer",
23532            ..FakeLspAdapter::default()
23533        },
23534    );
23535    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23536        "Rust",
23537        FakeLspAdapter {
23538            capabilities: lsp::ServerCapabilities {
23539                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23540                ..lsp::ServerCapabilities::default()
23541            },
23542            name: "not-rust-analyzer",
23543            ..FakeLspAdapter::default()
23544        },
23545    );
23546
23547    let editor = workspace
23548        .update(cx, |workspace, window, cx| {
23549            workspace.open_abs_path(
23550                PathBuf::from(path!("/a/first.rs")),
23551                OpenOptions::default(),
23552                window,
23553                cx,
23554            )
23555        })
23556        .unwrap()
23557        .await
23558        .unwrap()
23559        .downcast::<Editor>()
23560        .unwrap();
23561    let fake_language_server = fake_servers.next().await.unwrap();
23562    let fake_language_server_without_capabilities =
23563        fake_servers_without_capabilities.next().await.unwrap();
23564    let requests_made = Arc::new(AtomicUsize::new(0));
23565    let closure_requests_made = Arc::clone(&requests_made);
23566    let mut color_request_handle = fake_language_server
23567        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23568            let requests_made = Arc::clone(&closure_requests_made);
23569            async move {
23570                assert_eq!(
23571                    params.text_document.uri,
23572                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23573                );
23574                requests_made.fetch_add(1, atomic::Ordering::Release);
23575                Ok(vec![
23576                    lsp::ColorInformation {
23577                        range: lsp::Range {
23578                            start: lsp::Position {
23579                                line: 0,
23580                                character: 0,
23581                            },
23582                            end: lsp::Position {
23583                                line: 0,
23584                                character: 1,
23585                            },
23586                        },
23587                        color: lsp::Color {
23588                            red: 0.33,
23589                            green: 0.33,
23590                            blue: 0.33,
23591                            alpha: 0.33,
23592                        },
23593                    },
23594                    lsp::ColorInformation {
23595                        range: lsp::Range {
23596                            start: lsp::Position {
23597                                line: 0,
23598                                character: 0,
23599                            },
23600                            end: lsp::Position {
23601                                line: 0,
23602                                character: 1,
23603                            },
23604                        },
23605                        color: lsp::Color {
23606                            red: 0.33,
23607                            green: 0.33,
23608                            blue: 0.33,
23609                            alpha: 0.33,
23610                        },
23611                    },
23612                ])
23613            }
23614        });
23615
23616    let _handle = fake_language_server_without_capabilities
23617        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23618            panic!("Should not be called");
23619        });
23620    cx.executor().advance_clock(Duration::from_millis(100));
23621    color_request_handle.next().await.unwrap();
23622    cx.run_until_parked();
23623    assert_eq!(
23624        1,
23625        requests_made.load(atomic::Ordering::Acquire),
23626        "Should query for colors once per editor open"
23627    );
23628    editor.update_in(cx, |editor, _, cx| {
23629        assert_eq!(
23630            vec![expected_color],
23631            extract_color_inlays(editor, cx),
23632            "Should have an initial inlay"
23633        );
23634    });
23635
23636    // opening another file in a split should not influence the LSP query counter
23637    workspace
23638        .update(cx, |workspace, window, cx| {
23639            assert_eq!(
23640                workspace.panes().len(),
23641                1,
23642                "Should have one pane with one editor"
23643            );
23644            workspace.move_item_to_pane_in_direction(
23645                &MoveItemToPaneInDirection {
23646                    direction: SplitDirection::Right,
23647                    focus: false,
23648                    clone: true,
23649                },
23650                window,
23651                cx,
23652            );
23653        })
23654        .unwrap();
23655    cx.run_until_parked();
23656    workspace
23657        .update(cx, |workspace, _, cx| {
23658            let panes = workspace.panes();
23659            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23660            for pane in panes {
23661                let editor = pane
23662                    .read(cx)
23663                    .active_item()
23664                    .and_then(|item| item.downcast::<Editor>())
23665                    .expect("Should have opened an editor in each split");
23666                let editor_file = editor
23667                    .read(cx)
23668                    .buffer()
23669                    .read(cx)
23670                    .as_singleton()
23671                    .expect("test deals with singleton buffers")
23672                    .read(cx)
23673                    .file()
23674                    .expect("test buffese should have a file")
23675                    .path();
23676                assert_eq!(
23677                    editor_file.as_ref(),
23678                    Path::new("first.rs"),
23679                    "Both editors should be opened for the same file"
23680                )
23681            }
23682        })
23683        .unwrap();
23684
23685    cx.executor().advance_clock(Duration::from_millis(500));
23686    let save = editor.update_in(cx, |editor, window, cx| {
23687        editor.move_to_end(&MoveToEnd, window, cx);
23688        editor.handle_input("dirty", window, cx);
23689        editor.save(
23690            SaveOptions {
23691                format: true,
23692                autosave: true,
23693            },
23694            project.clone(),
23695            window,
23696            cx,
23697        )
23698    });
23699    save.await.unwrap();
23700
23701    color_request_handle.next().await.unwrap();
23702    cx.run_until_parked();
23703    assert_eq!(
23704        3,
23705        requests_made.load(atomic::Ordering::Acquire),
23706        "Should query for colors once per save and once per formatting after save"
23707    );
23708
23709    drop(editor);
23710    let close = workspace
23711        .update(cx, |workspace, window, cx| {
23712            workspace.active_pane().update(cx, |pane, cx| {
23713                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23714            })
23715        })
23716        .unwrap();
23717    close.await.unwrap();
23718    let close = workspace
23719        .update(cx, |workspace, window, cx| {
23720            workspace.active_pane().update(cx, |pane, cx| {
23721                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23722            })
23723        })
23724        .unwrap();
23725    close.await.unwrap();
23726    assert_eq!(
23727        3,
23728        requests_made.load(atomic::Ordering::Acquire),
23729        "After saving and closing all editors, no extra requests should be made"
23730    );
23731    workspace
23732        .update(cx, |workspace, _, cx| {
23733            assert!(
23734                workspace.active_item(cx).is_none(),
23735                "Should close all editors"
23736            )
23737        })
23738        .unwrap();
23739
23740    workspace
23741        .update(cx, |workspace, window, cx| {
23742            workspace.active_pane().update(cx, |pane, cx| {
23743                pane.navigate_backward(window, cx);
23744            })
23745        })
23746        .unwrap();
23747    cx.executor().advance_clock(Duration::from_millis(100));
23748    cx.run_until_parked();
23749    let editor = workspace
23750        .update(cx, |workspace, _, cx| {
23751            workspace
23752                .active_item(cx)
23753                .expect("Should have reopened the editor again after navigating back")
23754                .downcast::<Editor>()
23755                .expect("Should be an editor")
23756        })
23757        .unwrap();
23758    color_request_handle.next().await.unwrap();
23759    assert_eq!(
23760        3,
23761        requests_made.load(atomic::Ordering::Acquire),
23762        "Cache should be reused on buffer close and reopen"
23763    );
23764    editor.update(cx, |editor, cx| {
23765        assert_eq!(
23766            vec![expected_color],
23767            extract_color_inlays(editor, cx),
23768            "Should have an initial inlay"
23769        );
23770    });
23771}
23772
23773#[gpui::test]
23774async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23775    init_test(cx, |_| {});
23776    let (editor, cx) = cx.add_window_view(Editor::single_line);
23777    editor.update_in(cx, |editor, window, cx| {
23778        editor.set_text("oops\n\nwow\n", window, cx)
23779    });
23780    cx.run_until_parked();
23781    editor.update(cx, |editor, cx| {
23782        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23783    });
23784    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23785    cx.run_until_parked();
23786    editor.update(cx, |editor, cx| {
23787        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23788    });
23789}
23790
23791#[track_caller]
23792fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23793    editor
23794        .all_inlays(cx)
23795        .into_iter()
23796        .filter_map(|inlay| inlay.get_color())
23797        .map(Rgba::from)
23798        .collect()
23799}