editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    cx.set_state(indoc! {"
 4734        «implement-windows-supportˇ»
 4735    "});
 4736    cx.update_editor(|e, window, cx| {
 4737        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4738    });
 4739    cx.assert_editor_state(indoc! {"
 4740        «Implement windows supportˇ»
 4741    "});
 4742}
 4743
 4744#[gpui::test]
 4745async fn test_manipulate_text(cx: &mut TestAppContext) {
 4746    init_test(cx, |_| {});
 4747
 4748    let mut cx = EditorTestContext::new(cx).await;
 4749
 4750    // Test convert_to_upper_case()
 4751    cx.set_state(indoc! {"
 4752        «hello worldˇ»
 4753    "});
 4754    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4755    cx.assert_editor_state(indoc! {"
 4756        «HELLO WORLDˇ»
 4757    "});
 4758
 4759    // Test convert_to_lower_case()
 4760    cx.set_state(indoc! {"
 4761        «HELLO WORLDˇ»
 4762    "});
 4763    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4764    cx.assert_editor_state(indoc! {"
 4765        «hello worldˇ»
 4766    "});
 4767
 4768    // Test multiple line, single selection case
 4769    cx.set_state(indoc! {"
 4770        «The quick brown
 4771        fox jumps over
 4772        the lazy dogˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        «The Quick Brown
 4777        Fox Jumps Over
 4778        The Lazy Dogˇ»
 4779    "});
 4780
 4781    // Test multiple line, single selection case
 4782    cx.set_state(indoc! {"
 4783        «The quick brown
 4784        fox jumps over
 4785        the lazy dogˇ»
 4786    "});
 4787    cx.update_editor(|e, window, cx| {
 4788        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4789    });
 4790    cx.assert_editor_state(indoc! {"
 4791        «TheQuickBrown
 4792        FoxJumpsOver
 4793        TheLazyDogˇ»
 4794    "});
 4795
 4796    // From here on out, test more complex cases of manipulate_text()
 4797
 4798    // Test no selection case - should affect words cursors are in
 4799    // Cursor at beginning, middle, and end of word
 4800    cx.set_state(indoc! {"
 4801        ˇhello big beauˇtiful worldˇ
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4806    "});
 4807
 4808    // Test multiple selections on a single line and across multiple lines
 4809    cx.set_state(indoc! {"
 4810        «Theˇ» quick «brown
 4811        foxˇ» jumps «overˇ»
 4812        the «lazyˇ» dog
 4813    "});
 4814    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4815    cx.assert_editor_state(indoc! {"
 4816        «THEˇ» quick «BROWN
 4817        FOXˇ» jumps «OVERˇ»
 4818        the «LAZYˇ» dog
 4819    "});
 4820
 4821    // Test case where text length grows
 4822    cx.set_state(indoc! {"
 4823        «tschüߡ»
 4824    "});
 4825    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4826    cx.assert_editor_state(indoc! {"
 4827        «TSCHÜSSˇ»
 4828    "});
 4829
 4830    // Test to make sure we don't crash when text shrinks
 4831    cx.set_state(indoc! {"
 4832        aaa_bbbˇ
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «aaaBbbˇ»
 4839    "});
 4840
 4841    // Test to make sure we all aware of the fact that each word can grow and shrink
 4842    // Final selections should be aware of this fact
 4843    cx.set_state(indoc! {"
 4844        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4845    "});
 4846    cx.update_editor(|e, window, cx| {
 4847        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4848    });
 4849    cx.assert_editor_state(indoc! {"
 4850        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4851    "});
 4852
 4853    cx.set_state(indoc! {"
 4854        «hElLo, WoRld!ˇ»
 4855    "});
 4856    cx.update_editor(|e, window, cx| {
 4857        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4858    });
 4859    cx.assert_editor_state(indoc! {"
 4860        «HeLlO, wOrLD!ˇ»
 4861    "});
 4862}
 4863
 4864#[gpui::test]
 4865fn test_duplicate_line(cx: &mut TestAppContext) {
 4866    init_test(cx, |_| {});
 4867
 4868    let editor = cx.add_window(|window, cx| {
 4869        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4870        build_editor(buffer, window, cx)
 4871    });
 4872    _ = editor.update(cx, |editor, window, cx| {
 4873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4874            s.select_display_ranges([
 4875                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4876                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4877                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4878                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4879            ])
 4880        });
 4881        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4882        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4883        assert_eq!(
 4884            editor.selections.display_ranges(cx),
 4885            vec![
 4886                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4887                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4888                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4889                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4890            ]
 4891        );
 4892    });
 4893
 4894    let editor = cx.add_window(|window, cx| {
 4895        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4896        build_editor(buffer, window, cx)
 4897    });
 4898    _ = editor.update(cx, |editor, window, cx| {
 4899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4900            s.select_display_ranges([
 4901                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4902                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4903            ])
 4904        });
 4905        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4906        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4907        assert_eq!(
 4908            editor.selections.display_ranges(cx),
 4909            vec![
 4910                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4911                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4912            ]
 4913        );
 4914    });
 4915
 4916    // With `move_upwards` the selections stay in place, except for
 4917    // the lines inserted above them
 4918    let editor = cx.add_window(|window, cx| {
 4919        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4920        build_editor(buffer, window, cx)
 4921    });
 4922    _ = editor.update(cx, |editor, window, cx| {
 4923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4924            s.select_display_ranges([
 4925                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4926                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4927                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4928                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4929            ])
 4930        });
 4931        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4932        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4933        assert_eq!(
 4934            editor.selections.display_ranges(cx),
 4935            vec![
 4936                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4937                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4938                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4939                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4962            ]
 4963        );
 4964    });
 4965
 4966    let editor = cx.add_window(|window, cx| {
 4967        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4968        build_editor(buffer, window, cx)
 4969    });
 4970    _ = editor.update(cx, |editor, window, cx| {
 4971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4972            s.select_display_ranges([
 4973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4974                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4975            ])
 4976        });
 4977        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4978        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4979        assert_eq!(
 4980            editor.selections.display_ranges(cx),
 4981            vec![
 4982                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4983                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4984            ]
 4985        );
 4986    });
 4987}
 4988
 4989#[gpui::test]
 4990fn test_move_line_up_down(cx: &mut TestAppContext) {
 4991    init_test(cx, |_| {});
 4992
 4993    let editor = cx.add_window(|window, cx| {
 4994        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4995        build_editor(buffer, window, cx)
 4996    });
 4997    _ = editor.update(cx, |editor, window, cx| {
 4998        editor.fold_creases(
 4999            vec![
 5000                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5001                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5002                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5003            ],
 5004            true,
 5005            window,
 5006            cx,
 5007        );
 5008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5009            s.select_display_ranges([
 5010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5011                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5012                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5013                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5014            ])
 5015        });
 5016        assert_eq!(
 5017            editor.display_text(cx),
 5018            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5019        );
 5020
 5021        editor.move_line_up(&MoveLineUp, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5030                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5031                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5032                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_down(&MoveLineDown, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5064                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5065                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5066                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5067            ]
 5068        );
 5069    });
 5070
 5071    _ = editor.update(cx, |editor, window, cx| {
 5072        editor.move_line_up(&MoveLineUp, window, cx);
 5073        assert_eq!(
 5074            editor.display_text(cx),
 5075            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5076        );
 5077        assert_eq!(
 5078            editor.selections.display_ranges(cx),
 5079            vec![
 5080                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5081                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5082                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5083                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5084            ]
 5085        );
 5086    });
 5087}
 5088
 5089#[gpui::test]
 5090fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5091    init_test(cx, |_| {});
 5092    let editor = cx.add_window(|window, cx| {
 5093        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5094        build_editor(buffer, window, cx)
 5095    });
 5096    _ = editor.update(cx, |editor, window, cx| {
 5097        editor.fold_creases(
 5098            vec![Crease::simple(
 5099                Point::new(6, 4)..Point::new(7, 4),
 5100                FoldPlaceholder::test(),
 5101            )],
 5102            true,
 5103            window,
 5104            cx,
 5105        );
 5106        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5107            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5108        });
 5109        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5110        editor.move_line_up(&MoveLineUp, window, cx);
 5111        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5112        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5113    });
 5114}
 5115
 5116#[gpui::test]
 5117fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5118    init_test(cx, |_| {});
 5119
 5120    let editor = cx.add_window(|window, cx| {
 5121        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5122        build_editor(buffer, window, cx)
 5123    });
 5124    _ = editor.update(cx, |editor, window, cx| {
 5125        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5126        editor.insert_blocks(
 5127            [BlockProperties {
 5128                style: BlockStyle::Fixed,
 5129                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5130                height: Some(1),
 5131                render: Arc::new(|_| div().into_any()),
 5132                priority: 0,
 5133            }],
 5134            Some(Autoscroll::fit()),
 5135            cx,
 5136        );
 5137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5138            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5139        });
 5140        editor.move_line_down(&MoveLineDown, window, cx);
 5141    });
 5142}
 5143
 5144#[gpui::test]
 5145async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5146    init_test(cx, |_| {});
 5147
 5148    let mut cx = EditorTestContext::new(cx).await;
 5149    cx.set_state(
 5150        &"
 5151            ˇzero
 5152            one
 5153            two
 5154            three
 5155            four
 5156            five
 5157        "
 5158        .unindent(),
 5159    );
 5160
 5161    // Create a four-line block that replaces three lines of text.
 5162    cx.update_editor(|editor, window, cx| {
 5163        let snapshot = editor.snapshot(window, cx);
 5164        let snapshot = &snapshot.buffer_snapshot;
 5165        let placement = BlockPlacement::Replace(
 5166            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5167        );
 5168        editor.insert_blocks(
 5169            [BlockProperties {
 5170                placement,
 5171                height: Some(4),
 5172                style: BlockStyle::Sticky,
 5173                render: Arc::new(|_| gpui::div().into_any_element()),
 5174                priority: 0,
 5175            }],
 5176            None,
 5177            cx,
 5178        );
 5179    });
 5180
 5181    // Move down so that the cursor touches the block.
 5182    cx.update_editor(|editor, window, cx| {
 5183        editor.move_down(&Default::default(), window, cx);
 5184    });
 5185    cx.assert_editor_state(
 5186        &"
 5187            zero
 5188            «one
 5189            two
 5190            threeˇ»
 5191            four
 5192            five
 5193        "
 5194        .unindent(),
 5195    );
 5196
 5197    // Move down past the block.
 5198    cx.update_editor(|editor, window, cx| {
 5199        editor.move_down(&Default::default(), window, cx);
 5200    });
 5201    cx.assert_editor_state(
 5202        &"
 5203            zero
 5204            one
 5205            two
 5206            three
 5207            ˇfour
 5208            five
 5209        "
 5210        .unindent(),
 5211    );
 5212}
 5213
 5214#[gpui::test]
 5215fn test_transpose(cx: &mut TestAppContext) {
 5216    init_test(cx, |_| {});
 5217
 5218    _ = cx.add_window(|window, cx| {
 5219        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5220        editor.set_style(EditorStyle::default(), window, cx);
 5221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5222            s.select_ranges([1..1])
 5223        });
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "bac");
 5226        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5227
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "bca");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5231
 5232        editor.transpose(&Default::default(), window, cx);
 5233        assert_eq!(editor.text(cx), "bac");
 5234        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5235
 5236        editor
 5237    });
 5238
 5239    _ = cx.add_window(|window, cx| {
 5240        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5241        editor.set_style(EditorStyle::default(), window, cx);
 5242        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5243            s.select_ranges([3..3])
 5244        });
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "acb\nde");
 5247        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5248
 5249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5250            s.select_ranges([4..4])
 5251        });
 5252        editor.transpose(&Default::default(), window, cx);
 5253        assert_eq!(editor.text(cx), "acbd\ne");
 5254        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5255
 5256        editor.transpose(&Default::default(), window, cx);
 5257        assert_eq!(editor.text(cx), "acbde\n");
 5258        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5259
 5260        editor.transpose(&Default::default(), window, cx);
 5261        assert_eq!(editor.text(cx), "acbd\ne");
 5262        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5263
 5264        editor
 5265    });
 5266
 5267    _ = cx.add_window(|window, cx| {
 5268        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5269        editor.set_style(EditorStyle::default(), window, cx);
 5270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5271            s.select_ranges([1..1, 2..2, 4..4])
 5272        });
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bacd\ne");
 5275        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bcade\n");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5280
 5281        editor.transpose(&Default::default(), window, cx);
 5282        assert_eq!(editor.text(cx), "bcda\ne");
 5283        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5284
 5285        editor.transpose(&Default::default(), window, cx);
 5286        assert_eq!(editor.text(cx), "bcade\n");
 5287        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5288
 5289        editor.transpose(&Default::default(), window, cx);
 5290        assert_eq!(editor.text(cx), "bcaed\n");
 5291        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5292
 5293        editor
 5294    });
 5295
 5296    _ = cx.add_window(|window, cx| {
 5297        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5298        editor.set_style(EditorStyle::default(), window, cx);
 5299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5300            s.select_ranges([4..4])
 5301        });
 5302        editor.transpose(&Default::default(), window, cx);
 5303        assert_eq!(editor.text(cx), "🏀🍐✋");
 5304        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5305
 5306        editor.transpose(&Default::default(), window, cx);
 5307        assert_eq!(editor.text(cx), "🏀✋🍐");
 5308        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5309
 5310        editor.transpose(&Default::default(), window, cx);
 5311        assert_eq!(editor.text(cx), "🏀🍐✋");
 5312        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5313
 5314        editor
 5315    });
 5316}
 5317
 5318#[gpui::test]
 5319async fn test_rewrap(cx: &mut TestAppContext) {
 5320    init_test(cx, |settings| {
 5321        settings.languages.0.extend([
 5322            (
 5323                "Markdown".into(),
 5324                LanguageSettingsContent {
 5325                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5326                    preferred_line_length: Some(40),
 5327                    ..Default::default()
 5328                },
 5329            ),
 5330            (
 5331                "Plain Text".into(),
 5332                LanguageSettingsContent {
 5333                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5334                    preferred_line_length: Some(40),
 5335                    ..Default::default()
 5336                },
 5337            ),
 5338            (
 5339                "C++".into(),
 5340                LanguageSettingsContent {
 5341                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5342                    preferred_line_length: Some(40),
 5343                    ..Default::default()
 5344                },
 5345            ),
 5346            (
 5347                "Python".into(),
 5348                LanguageSettingsContent {
 5349                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5350                    preferred_line_length: Some(40),
 5351                    ..Default::default()
 5352                },
 5353            ),
 5354            (
 5355                "Rust".into(),
 5356                LanguageSettingsContent {
 5357                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5358                    preferred_line_length: Some(40),
 5359                    ..Default::default()
 5360                },
 5361            ),
 5362        ])
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    let cpp_language = Arc::new(Language::new(
 5368        LanguageConfig {
 5369            name: "C++".into(),
 5370            line_comments: vec!["// ".into()],
 5371            ..LanguageConfig::default()
 5372        },
 5373        None,
 5374    ));
 5375    let python_language = Arc::new(Language::new(
 5376        LanguageConfig {
 5377            name: "Python".into(),
 5378            line_comments: vec!["# ".into()],
 5379            ..LanguageConfig::default()
 5380        },
 5381        None,
 5382    ));
 5383    let markdown_language = Arc::new(Language::new(
 5384        LanguageConfig {
 5385            name: "Markdown".into(),
 5386            rewrap_prefixes: vec![
 5387                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5388                regex::Regex::new("[-*+]\\s+").unwrap(),
 5389            ],
 5390            ..LanguageConfig::default()
 5391        },
 5392        None,
 5393    ));
 5394    let rust_language = Arc::new(Language::new(
 5395        LanguageConfig {
 5396            name: "Rust".into(),
 5397            line_comments: vec!["// ".into(), "/// ".into()],
 5398            ..LanguageConfig::default()
 5399        },
 5400        Some(tree_sitter_rust::LANGUAGE.into()),
 5401    ));
 5402
 5403    let plaintext_language = Arc::new(Language::new(
 5404        LanguageConfig {
 5405            name: "Plain Text".into(),
 5406            ..LanguageConfig::default()
 5407        },
 5408        None,
 5409    ));
 5410
 5411    // Test basic rewrapping of a long line with a cursor
 5412    assert_rewrap(
 5413        indoc! {"
 5414            // ˇThis is a long comment that needs to be wrapped.
 5415        "},
 5416        indoc! {"
 5417            // ˇThis is a long comment that needs to
 5418            // be wrapped.
 5419        "},
 5420        cpp_language.clone(),
 5421        &mut cx,
 5422    );
 5423
 5424    // Test rewrapping a full selection
 5425    assert_rewrap(
 5426        indoc! {"
 5427            «// This selected long comment needs to be wrapped.ˇ»"
 5428        },
 5429        indoc! {"
 5430            «// This selected long comment needs to
 5431            // be wrapped.ˇ»"
 5432        },
 5433        cpp_language.clone(),
 5434        &mut cx,
 5435    );
 5436
 5437    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5438    assert_rewrap(
 5439        indoc! {"
 5440            // ˇThis is the first line.
 5441            // Thisˇ is the second line.
 5442            // This is the thirdˇ line, all part of one paragraph.
 5443         "},
 5444        indoc! {"
 5445            // ˇThis is the first line. Thisˇ is the
 5446            // second line. This is the thirdˇ line,
 5447            // all part of one paragraph.
 5448         "},
 5449        cpp_language.clone(),
 5450        &mut cx,
 5451    );
 5452
 5453    // Test multiple cursors in different paragraphs trigger separate rewraps
 5454    assert_rewrap(
 5455        indoc! {"
 5456            // ˇThis is the first paragraph, first line.
 5457            // ˇThis is the first paragraph, second line.
 5458
 5459            // ˇThis is the second paragraph, first line.
 5460            // ˇThis is the second paragraph, second line.
 5461        "},
 5462        indoc! {"
 5463            // ˇThis is the first paragraph, first
 5464            // line. ˇThis is the first paragraph,
 5465            // second line.
 5466
 5467            // ˇThis is the second paragraph, first
 5468            // line. ˇThis is the second paragraph,
 5469            // second line.
 5470        "},
 5471        cpp_language.clone(),
 5472        &mut cx,
 5473    );
 5474
 5475    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5476    assert_rewrap(
 5477        indoc! {"
 5478            «// A regular long long comment to be wrapped.
 5479            /// A documentation long comment to be wrapped.ˇ»
 5480          "},
 5481        indoc! {"
 5482            «// A regular long long comment to be
 5483            // wrapped.
 5484            /// A documentation long comment to be
 5485            /// wrapped.ˇ»
 5486          "},
 5487        rust_language.clone(),
 5488        &mut cx,
 5489    );
 5490
 5491    // Test that change in indentation level trigger seperate rewraps
 5492    assert_rewrap(
 5493        indoc! {"
 5494            fn foo() {
 5495                «// This is a long comment at the base indent.
 5496                    // This is a long comment at the next indent.ˇ»
 5497            }
 5498        "},
 5499        indoc! {"
 5500            fn foo() {
 5501                «// This is a long comment at the
 5502                // base indent.
 5503                    // This is a long comment at the
 5504                    // next indent.ˇ»
 5505            }
 5506        "},
 5507        rust_language.clone(),
 5508        &mut cx,
 5509    );
 5510
 5511    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5512    assert_rewrap(
 5513        indoc! {"
 5514            # ˇThis is a long comment using a pound sign.
 5515        "},
 5516        indoc! {"
 5517            # ˇThis is a long comment using a pound
 5518            # sign.
 5519        "},
 5520        python_language.clone(),
 5521        &mut cx,
 5522    );
 5523
 5524    // Test rewrapping only affects comments, not code even when selected
 5525    assert_rewrap(
 5526        indoc! {"
 5527            «/// This doc comment is long and should be wrapped.
 5528            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5529        "},
 5530        indoc! {"
 5531            «/// This doc comment is long and should
 5532            /// be wrapped.
 5533            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5534        "},
 5535        rust_language.clone(),
 5536        &mut cx,
 5537    );
 5538
 5539    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5540    assert_rewrap(
 5541        indoc! {"
 5542            # Header
 5543
 5544            A long long long line of markdown text to wrap.ˇ
 5545         "},
 5546        indoc! {"
 5547            # Header
 5548
 5549            A long long long line of markdown text
 5550            to wrap.ˇ
 5551         "},
 5552        markdown_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5557    assert_rewrap(
 5558        indoc! {"
 5559            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5560            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5561            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5562        "},
 5563        indoc! {"
 5564            «1. This is a numbered list item that is
 5565               very long and needs to be wrapped
 5566               properly.
 5567            2. This is a numbered list item that is
 5568               very long and needs to be wrapped
 5569               properly.
 5570            - This is an unordered list item that is
 5571              also very long and should not merge
 5572              with the numbered item.ˇ»
 5573        "},
 5574        markdown_language.clone(),
 5575        &mut cx,
 5576    );
 5577
 5578    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5579    assert_rewrap(
 5580        indoc! {"
 5581            «1. This is a numbered list item that is
 5582            very long and needs to be wrapped
 5583            properly.
 5584            2. This is a numbered list item that is
 5585            very long and needs to be wrapped
 5586            properly.
 5587            - This is an unordered list item that is
 5588            also very long and should not merge with
 5589            the numbered item.ˇ»
 5590        "},
 5591        indoc! {"
 5592            «1. This is a numbered list item that is
 5593               very long and needs to be wrapped
 5594               properly.
 5595            2. This is a numbered list item that is
 5596               very long and needs to be wrapped
 5597               properly.
 5598            - This is an unordered list item that is
 5599              also very long and should not merge
 5600              with the numbered item.ˇ»
 5601        "},
 5602        markdown_language.clone(),
 5603        &mut cx,
 5604    );
 5605
 5606    // Test that rewrapping maintain indents even when they already exists.
 5607    assert_rewrap(
 5608        indoc! {"
 5609            «1. This is a numbered list
 5610               item that is very long and needs to be wrapped properly.
 5611            2. This is a numbered list
 5612               item that is very long and needs to be wrapped properly.
 5613            - This is an unordered list item that is also very long and
 5614              should not merge with the numbered item.ˇ»
 5615        "},
 5616        indoc! {"
 5617            «1. This is a numbered list item that is
 5618               very long and needs to be wrapped
 5619               properly.
 5620            2. This is a numbered list item that is
 5621               very long and needs to be wrapped
 5622               properly.
 5623            - This is an unordered list item that is
 5624              also very long and should not merge
 5625              with the numbered item.ˇ»
 5626        "},
 5627        markdown_language.clone(),
 5628        &mut cx,
 5629    );
 5630
 5631    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5632    assert_rewrap(
 5633        indoc! {"
 5634            ˇThis is a very long line of plain text that will be wrapped.
 5635        "},
 5636        indoc! {"
 5637            ˇThis is a very long line of plain text
 5638            that will be wrapped.
 5639        "},
 5640        plaintext_language.clone(),
 5641        &mut cx,
 5642    );
 5643
 5644    // Test that non-commented code acts as a paragraph boundary within a selection
 5645    assert_rewrap(
 5646        indoc! {"
 5647               «// This is the first long comment block to be wrapped.
 5648               fn my_func(a: u32);
 5649               // This is the second long comment block to be wrapped.ˇ»
 5650           "},
 5651        indoc! {"
 5652               «// This is the first long comment block
 5653               // to be wrapped.
 5654               fn my_func(a: u32);
 5655               // This is the second long comment block
 5656               // to be wrapped.ˇ»
 5657           "},
 5658        rust_language.clone(),
 5659        &mut cx,
 5660    );
 5661
 5662    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5663    assert_rewrap(
 5664        indoc! {"
 5665            «ˇThis is a very long line that will be wrapped.
 5666
 5667            This is another paragraph in the same selection.»
 5668
 5669            «\tThis is a very long indented line that will be wrapped.ˇ»
 5670         "},
 5671        indoc! {"
 5672            «ˇThis is a very long line that will be
 5673            wrapped.
 5674
 5675            This is another paragraph in the same
 5676            selection.»
 5677
 5678            «\tThis is a very long indented line
 5679            \tthat will be wrapped.ˇ»
 5680         "},
 5681        plaintext_language.clone(),
 5682        &mut cx,
 5683    );
 5684
 5685    // Test that an empty comment line acts as a paragraph boundary
 5686    assert_rewrap(
 5687        indoc! {"
 5688            // ˇThis is a long comment that will be wrapped.
 5689            //
 5690            // And this is another long comment that will also be wrapped.ˇ
 5691         "},
 5692        indoc! {"
 5693            // ˇThis is a long comment that will be
 5694            // wrapped.
 5695            //
 5696            // And this is another long comment that
 5697            // will also be wrapped.ˇ
 5698         "},
 5699        cpp_language,
 5700        &mut cx,
 5701    );
 5702
 5703    #[track_caller]
 5704    fn assert_rewrap(
 5705        unwrapped_text: &str,
 5706        wrapped_text: &str,
 5707        language: Arc<Language>,
 5708        cx: &mut EditorTestContext,
 5709    ) {
 5710        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5711        cx.set_state(unwrapped_text);
 5712        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5713        cx.assert_editor_state(wrapped_text);
 5714    }
 5715}
 5716
 5717#[gpui::test]
 5718async fn test_hard_wrap(cx: &mut TestAppContext) {
 5719    init_test(cx, |_| {});
 5720    let mut cx = EditorTestContext::new(cx).await;
 5721
 5722    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5723    cx.update_editor(|editor, _, cx| {
 5724        editor.set_hard_wrap(Some(14), cx);
 5725    });
 5726
 5727    cx.set_state(indoc!(
 5728        "
 5729        one two three ˇ
 5730        "
 5731    ));
 5732    cx.simulate_input("four");
 5733    cx.run_until_parked();
 5734
 5735    cx.assert_editor_state(indoc!(
 5736        "
 5737        one two three
 5738        fourˇ
 5739        "
 5740    ));
 5741
 5742    cx.update_editor(|editor, window, cx| {
 5743        editor.newline(&Default::default(), window, cx);
 5744    });
 5745    cx.run_until_parked();
 5746    cx.assert_editor_state(indoc!(
 5747        "
 5748        one two three
 5749        four
 5750        ˇ
 5751        "
 5752    ));
 5753
 5754    cx.simulate_input("five");
 5755    cx.run_until_parked();
 5756    cx.assert_editor_state(indoc!(
 5757        "
 5758        one two three
 5759        four
 5760        fiveˇ
 5761        "
 5762    ));
 5763
 5764    cx.update_editor(|editor, window, cx| {
 5765        editor.newline(&Default::default(), window, cx);
 5766    });
 5767    cx.run_until_parked();
 5768    cx.simulate_input("# ");
 5769    cx.run_until_parked();
 5770    cx.assert_editor_state(indoc!(
 5771        "
 5772        one two three
 5773        four
 5774        five
 5775        # ˇ
 5776        "
 5777    ));
 5778
 5779    cx.update_editor(|editor, window, cx| {
 5780        editor.newline(&Default::default(), window, cx);
 5781    });
 5782    cx.run_until_parked();
 5783    cx.assert_editor_state(indoc!(
 5784        "
 5785        one two three
 5786        four
 5787        five
 5788        #\x20
 5789 5790        "
 5791    ));
 5792
 5793    cx.simulate_input(" 6");
 5794    cx.run_until_parked();
 5795    cx.assert_editor_state(indoc!(
 5796        "
 5797        one two three
 5798        four
 5799        five
 5800        #
 5801        # 6ˇ
 5802        "
 5803    ));
 5804}
 5805
 5806#[gpui::test]
 5807async fn test_clipboard(cx: &mut TestAppContext) {
 5808    init_test(cx, |_| {});
 5809
 5810    let mut cx = EditorTestContext::new(cx).await;
 5811
 5812    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5813    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5814    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5815
 5816    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5817    cx.set_state("two ˇfour ˇsix ˇ");
 5818    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5819    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5820
 5821    // Paste again but with only two cursors. Since the number of cursors doesn't
 5822    // match the number of slices in the clipboard, the entire clipboard text
 5823    // is pasted at each cursor.
 5824    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5825    cx.update_editor(|e, window, cx| {
 5826        e.handle_input("( ", window, cx);
 5827        e.paste(&Paste, window, cx);
 5828        e.handle_input(") ", window, cx);
 5829    });
 5830    cx.assert_editor_state(
 5831        &([
 5832            "( one✅ ",
 5833            "three ",
 5834            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5835            "three ",
 5836            "five ) ˇ",
 5837        ]
 5838        .join("\n")),
 5839    );
 5840
 5841    // Cut with three selections, one of which is full-line.
 5842    cx.set_state(indoc! {"
 5843        1«2ˇ»3
 5844        4ˇ567
 5845        «8ˇ»9"});
 5846    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5847    cx.assert_editor_state(indoc! {"
 5848        1ˇ3
 5849        ˇ9"});
 5850
 5851    // Paste with three selections, noticing how the copied selection that was full-line
 5852    // gets inserted before the second cursor.
 5853    cx.set_state(indoc! {"
 5854        1ˇ3
 5855 5856        «oˇ»ne"});
 5857    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5858    cx.assert_editor_state(indoc! {"
 5859        12ˇ3
 5860        4567
 5861 5862        8ˇne"});
 5863
 5864    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5865    cx.set_state(indoc! {"
 5866        The quick brown
 5867        fox juˇmps over
 5868        the lazy dog"});
 5869    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5870    assert_eq!(
 5871        cx.read_from_clipboard()
 5872            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5873        Some("fox jumps over\n".to_string())
 5874    );
 5875
 5876    // Paste with three selections, noticing how the copied full-line selection is inserted
 5877    // before the empty selections but replaces the selection that is non-empty.
 5878    cx.set_state(indoc! {"
 5879        Tˇhe quick brown
 5880        «foˇ»x jumps over
 5881        tˇhe lazy dog"});
 5882    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5883    cx.assert_editor_state(indoc! {"
 5884        fox jumps over
 5885        Tˇhe quick brown
 5886        fox jumps over
 5887        ˇx jumps over
 5888        fox jumps over
 5889        tˇhe lazy dog"});
 5890}
 5891
 5892#[gpui::test]
 5893async fn test_copy_trim(cx: &mut TestAppContext) {
 5894    init_test(cx, |_| {});
 5895
 5896    let mut cx = EditorTestContext::new(cx).await;
 5897    cx.set_state(
 5898        r#"            «for selection in selections.iter() {
 5899            let mut start = selection.start;
 5900            let mut end = selection.end;
 5901            let is_entire_line = selection.is_empty();
 5902            if is_entire_line {
 5903                start = Point::new(start.row, 0);ˇ»
 5904                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5905            }
 5906        "#,
 5907    );
 5908    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5909    assert_eq!(
 5910        cx.read_from_clipboard()
 5911            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5912        Some(
 5913            "for selection in selections.iter() {
 5914            let mut start = selection.start;
 5915            let mut end = selection.end;
 5916            let is_entire_line = selection.is_empty();
 5917            if is_entire_line {
 5918                start = Point::new(start.row, 0);"
 5919                .to_string()
 5920        ),
 5921        "Regular copying preserves all indentation selected",
 5922    );
 5923    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5924    assert_eq!(
 5925        cx.read_from_clipboard()
 5926            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5927        Some(
 5928            "for selection in selections.iter() {
 5929let mut start = selection.start;
 5930let mut end = selection.end;
 5931let is_entire_line = selection.is_empty();
 5932if is_entire_line {
 5933    start = Point::new(start.row, 0);"
 5934                .to_string()
 5935        ),
 5936        "Copying with stripping should strip all leading whitespaces"
 5937    );
 5938
 5939    cx.set_state(
 5940        r#"       «     for selection in selections.iter() {
 5941            let mut start = selection.start;
 5942            let mut end = selection.end;
 5943            let is_entire_line = selection.is_empty();
 5944            if is_entire_line {
 5945                start = Point::new(start.row, 0);ˇ»
 5946                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5947            }
 5948        "#,
 5949    );
 5950    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5951    assert_eq!(
 5952        cx.read_from_clipboard()
 5953            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5954        Some(
 5955            "     for selection in selections.iter() {
 5956            let mut start = selection.start;
 5957            let mut end = selection.end;
 5958            let is_entire_line = selection.is_empty();
 5959            if is_entire_line {
 5960                start = Point::new(start.row, 0);"
 5961                .to_string()
 5962        ),
 5963        "Regular copying preserves all indentation selected",
 5964    );
 5965    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5966    assert_eq!(
 5967        cx.read_from_clipboard()
 5968            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5969        Some(
 5970            "for selection in selections.iter() {
 5971let mut start = selection.start;
 5972let mut end = selection.end;
 5973let is_entire_line = selection.is_empty();
 5974if is_entire_line {
 5975    start = Point::new(start.row, 0);"
 5976                .to_string()
 5977        ),
 5978        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5979    );
 5980
 5981    cx.set_state(
 5982        r#"       «ˇ     for selection in selections.iter() {
 5983            let mut start = selection.start;
 5984            let mut end = selection.end;
 5985            let is_entire_line = selection.is_empty();
 5986            if is_entire_line {
 5987                start = Point::new(start.row, 0);»
 5988                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5989            }
 5990        "#,
 5991    );
 5992    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5993    assert_eq!(
 5994        cx.read_from_clipboard()
 5995            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5996        Some(
 5997            "     for selection in selections.iter() {
 5998            let mut start = selection.start;
 5999            let mut end = selection.end;
 6000            let is_entire_line = selection.is_empty();
 6001            if is_entire_line {
 6002                start = Point::new(start.row, 0);"
 6003                .to_string()
 6004        ),
 6005        "Regular copying for reverse selection works the same",
 6006    );
 6007    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6008    assert_eq!(
 6009        cx.read_from_clipboard()
 6010            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6011        Some(
 6012            "for selection in selections.iter() {
 6013let mut start = selection.start;
 6014let mut end = selection.end;
 6015let is_entire_line = selection.is_empty();
 6016if is_entire_line {
 6017    start = Point::new(start.row, 0);"
 6018                .to_string()
 6019        ),
 6020        "Copying with stripping for reverse selection works the same"
 6021    );
 6022
 6023    cx.set_state(
 6024        r#"            for selection «in selections.iter() {
 6025            let mut start = selection.start;
 6026            let mut end = selection.end;
 6027            let is_entire_line = selection.is_empty();
 6028            if is_entire_line {
 6029                start = Point::new(start.row, 0);ˇ»
 6030                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6031            }
 6032        "#,
 6033    );
 6034    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6035    assert_eq!(
 6036        cx.read_from_clipboard()
 6037            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6038        Some(
 6039            "in selections.iter() {
 6040            let mut start = selection.start;
 6041            let mut end = selection.end;
 6042            let is_entire_line = selection.is_empty();
 6043            if is_entire_line {
 6044                start = Point::new(start.row, 0);"
 6045                .to_string()
 6046        ),
 6047        "When selecting past the indent, the copying works as usual",
 6048    );
 6049    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6050    assert_eq!(
 6051        cx.read_from_clipboard()
 6052            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6053        Some(
 6054            "in selections.iter() {
 6055            let mut start = selection.start;
 6056            let mut end = selection.end;
 6057            let is_entire_line = selection.is_empty();
 6058            if is_entire_line {
 6059                start = Point::new(start.row, 0);"
 6060                .to_string()
 6061        ),
 6062        "When selecting past the indent, nothing is trimmed"
 6063    );
 6064
 6065    cx.set_state(
 6066        r#"            «for selection in selections.iter() {
 6067            let mut start = selection.start;
 6068
 6069            let mut end = selection.end;
 6070            let is_entire_line = selection.is_empty();
 6071            if is_entire_line {
 6072                start = Point::new(start.row, 0);
 6073ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6074            }
 6075        "#,
 6076    );
 6077    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6078    assert_eq!(
 6079        cx.read_from_clipboard()
 6080            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6081        Some(
 6082            "for selection in selections.iter() {
 6083let mut start = selection.start;
 6084
 6085let mut end = selection.end;
 6086let is_entire_line = selection.is_empty();
 6087if is_entire_line {
 6088    start = Point::new(start.row, 0);
 6089"
 6090            .to_string()
 6091        ),
 6092        "Copying with stripping should ignore empty lines"
 6093    );
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_paste_multiline(cx: &mut TestAppContext) {
 6098    init_test(cx, |_| {});
 6099
 6100    let mut cx = EditorTestContext::new(cx).await;
 6101    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6102
 6103    // Cut an indented block, without the leading whitespace.
 6104    cx.set_state(indoc! {"
 6105        const a: B = (
 6106            c(),
 6107            «d(
 6108                e,
 6109                f
 6110            )ˇ»
 6111        );
 6112    "});
 6113    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6114    cx.assert_editor_state(indoc! {"
 6115        const a: B = (
 6116            c(),
 6117            ˇ
 6118        );
 6119    "});
 6120
 6121    // Paste it at the same position.
 6122    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6123    cx.assert_editor_state(indoc! {"
 6124        const a: B = (
 6125            c(),
 6126            d(
 6127                e,
 6128                f
 6129 6130        );
 6131    "});
 6132
 6133    // Paste it at a line with a lower indent level.
 6134    cx.set_state(indoc! {"
 6135        ˇ
 6136        const a: B = (
 6137            c(),
 6138        );
 6139    "});
 6140    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6141    cx.assert_editor_state(indoc! {"
 6142        d(
 6143            e,
 6144            f
 6145 6146        const a: B = (
 6147            c(),
 6148        );
 6149    "});
 6150
 6151    // Cut an indented block, with the leading whitespace.
 6152    cx.set_state(indoc! {"
 6153        const a: B = (
 6154            c(),
 6155        «    d(
 6156                e,
 6157                f
 6158            )
 6159        ˇ»);
 6160    "});
 6161    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6162    cx.assert_editor_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165        ˇ);
 6166    "});
 6167
 6168    // Paste it at the same position.
 6169    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6170    cx.assert_editor_state(indoc! {"
 6171        const a: B = (
 6172            c(),
 6173            d(
 6174                e,
 6175                f
 6176            )
 6177        ˇ);
 6178    "});
 6179
 6180    // Paste it at a line with a higher indent level.
 6181    cx.set_state(indoc! {"
 6182        const a: B = (
 6183            c(),
 6184            d(
 6185                e,
 6186 6187            )
 6188        );
 6189    "});
 6190    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6191    cx.assert_editor_state(indoc! {"
 6192        const a: B = (
 6193            c(),
 6194            d(
 6195                e,
 6196                f    d(
 6197                    e,
 6198                    f
 6199                )
 6200        ˇ
 6201            )
 6202        );
 6203    "});
 6204
 6205    // Copy an indented block, starting mid-line
 6206    cx.set_state(indoc! {"
 6207        const a: B = (
 6208            c(),
 6209            somethin«g(
 6210                e,
 6211                f
 6212            )ˇ»
 6213        );
 6214    "});
 6215    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6216
 6217    // Paste it on a line with a lower indent level
 6218    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6219    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6220    cx.assert_editor_state(indoc! {"
 6221        const a: B = (
 6222            c(),
 6223            something(
 6224                e,
 6225                f
 6226            )
 6227        );
 6228        g(
 6229            e,
 6230            f
 6231"});
 6232}
 6233
 6234#[gpui::test]
 6235async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6236    init_test(cx, |_| {});
 6237
 6238    cx.write_to_clipboard(ClipboardItem::new_string(
 6239        "    d(\n        e\n    );\n".into(),
 6240    ));
 6241
 6242    let mut cx = EditorTestContext::new(cx).await;
 6243    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6244
 6245    cx.set_state(indoc! {"
 6246        fn a() {
 6247            b();
 6248            if c() {
 6249                ˇ
 6250            }
 6251        }
 6252    "});
 6253
 6254    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6255    cx.assert_editor_state(indoc! {"
 6256        fn a() {
 6257            b();
 6258            if c() {
 6259                d(
 6260                    e
 6261                );
 6262        ˇ
 6263            }
 6264        }
 6265    "});
 6266
 6267    cx.set_state(indoc! {"
 6268        fn a() {
 6269            b();
 6270            ˇ
 6271        }
 6272    "});
 6273
 6274    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6275    cx.assert_editor_state(indoc! {"
 6276        fn a() {
 6277            b();
 6278            d(
 6279                e
 6280            );
 6281        ˇ
 6282        }
 6283    "});
 6284}
 6285
 6286#[gpui::test]
 6287fn test_select_all(cx: &mut TestAppContext) {
 6288    init_test(cx, |_| {});
 6289
 6290    let editor = cx.add_window(|window, cx| {
 6291        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6292        build_editor(buffer, window, cx)
 6293    });
 6294    _ = editor.update(cx, |editor, window, cx| {
 6295        editor.select_all(&SelectAll, window, cx);
 6296        assert_eq!(
 6297            editor.selections.display_ranges(cx),
 6298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6299        );
 6300    });
 6301}
 6302
 6303#[gpui::test]
 6304fn test_select_line(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306
 6307    let editor = cx.add_window(|window, cx| {
 6308        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6309        build_editor(buffer, window, cx)
 6310    });
 6311    _ = editor.update(cx, |editor, window, cx| {
 6312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6313            s.select_display_ranges([
 6314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6315                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6316                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6317                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6318            ])
 6319        });
 6320        editor.select_line(&SelectLine, window, cx);
 6321        assert_eq!(
 6322            editor.selections.display_ranges(cx),
 6323            vec![
 6324                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6325                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6326            ]
 6327        );
 6328    });
 6329
 6330    _ = editor.update(cx, |editor, window, cx| {
 6331        editor.select_line(&SelectLine, window, cx);
 6332        assert_eq!(
 6333            editor.selections.display_ranges(cx),
 6334            vec![
 6335                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6336                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6337            ]
 6338        );
 6339    });
 6340
 6341    _ = editor.update(cx, |editor, window, cx| {
 6342        editor.select_line(&SelectLine, window, cx);
 6343        assert_eq!(
 6344            editor.selections.display_ranges(cx),
 6345            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6346        );
 6347    });
 6348}
 6349
 6350#[gpui::test]
 6351async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6352    init_test(cx, |_| {});
 6353    let mut cx = EditorTestContext::new(cx).await;
 6354
 6355    #[track_caller]
 6356    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6357        cx.set_state(initial_state);
 6358        cx.update_editor(|e, window, cx| {
 6359            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6360        });
 6361        cx.assert_editor_state(expected_state);
 6362    }
 6363
 6364    // Selection starts and ends at the middle of lines, left-to-right
 6365    test(
 6366        &mut cx,
 6367        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6368        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6369    );
 6370    // Same thing, right-to-left
 6371    test(
 6372        &mut cx,
 6373        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6374        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6375    );
 6376
 6377    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6378    test(
 6379        &mut cx,
 6380        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6381        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6382    );
 6383    // Same thing, right-to-left
 6384    test(
 6385        &mut cx,
 6386        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6388    );
 6389
 6390    // Whole buffer, left-to-right, last line ends with newline
 6391    test(
 6392        &mut cx,
 6393        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6394        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6395    );
 6396    // Same thing, right-to-left
 6397    test(
 6398        &mut cx,
 6399        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6400        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6401    );
 6402
 6403    // Starts at the end of a line, ends at the start of another
 6404    test(
 6405        &mut cx,
 6406        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6407        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6408    );
 6409}
 6410
 6411#[gpui::test]
 6412async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6413    init_test(cx, |_| {});
 6414
 6415    let editor = cx.add_window(|window, cx| {
 6416        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6417        build_editor(buffer, window, cx)
 6418    });
 6419
 6420    // setup
 6421    _ = editor.update(cx, |editor, window, cx| {
 6422        editor.fold_creases(
 6423            vec![
 6424                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6425                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6426                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6427            ],
 6428            true,
 6429            window,
 6430            cx,
 6431        );
 6432        assert_eq!(
 6433            editor.display_text(cx),
 6434            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6435        );
 6436    });
 6437
 6438    _ = editor.update(cx, |editor, window, cx| {
 6439        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6440            s.select_display_ranges([
 6441                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6442                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6443                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6444                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6445            ])
 6446        });
 6447        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6448        assert_eq!(
 6449            editor.display_text(cx),
 6450            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6451        );
 6452    });
 6453    EditorTestContext::for_editor(editor, cx)
 6454        .await
 6455        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6456
 6457    _ = editor.update(cx, |editor, window, cx| {
 6458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6459            s.select_display_ranges([
 6460                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6461            ])
 6462        });
 6463        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6464        assert_eq!(
 6465            editor.display_text(cx),
 6466            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6467        );
 6468        assert_eq!(
 6469            editor.selections.display_ranges(cx),
 6470            [
 6471                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6472                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6473                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6474                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6475                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6476                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6477                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6478            ]
 6479        );
 6480    });
 6481    EditorTestContext::for_editor(editor, cx)
 6482        .await
 6483        .assert_editor_state(
 6484            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6485        );
 6486}
 6487
 6488#[gpui::test]
 6489async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6490    init_test(cx, |_| {});
 6491
 6492    let mut cx = EditorTestContext::new(cx).await;
 6493
 6494    cx.set_state(indoc!(
 6495        r#"abc
 6496           defˇghi
 6497
 6498           jk
 6499           nlmo
 6500           "#
 6501    ));
 6502
 6503    cx.update_editor(|editor, window, cx| {
 6504        editor.add_selection_above(&Default::default(), window, cx);
 6505    });
 6506
 6507    cx.assert_editor_state(indoc!(
 6508        r#"abcˇ
 6509           defˇghi
 6510
 6511           jk
 6512           nlmo
 6513           "#
 6514    ));
 6515
 6516    cx.update_editor(|editor, window, cx| {
 6517        editor.add_selection_above(&Default::default(), window, cx);
 6518    });
 6519
 6520    cx.assert_editor_state(indoc!(
 6521        r#"abcˇ
 6522            defˇghi
 6523
 6524            jk
 6525            nlmo
 6526            "#
 6527    ));
 6528
 6529    cx.update_editor(|editor, window, cx| {
 6530        editor.add_selection_below(&Default::default(), window, cx);
 6531    });
 6532
 6533    cx.assert_editor_state(indoc!(
 6534        r#"abc
 6535           defˇghi
 6536
 6537           jk
 6538           nlmo
 6539           "#
 6540    ));
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.undo_selection(&Default::default(), window, cx);
 6544    });
 6545
 6546    cx.assert_editor_state(indoc!(
 6547        r#"abcˇ
 6548           defˇghi
 6549
 6550           jk
 6551           nlmo
 6552           "#
 6553    ));
 6554
 6555    cx.update_editor(|editor, window, cx| {
 6556        editor.redo_selection(&Default::default(), window, cx);
 6557    });
 6558
 6559    cx.assert_editor_state(indoc!(
 6560        r#"abc
 6561           defˇghi
 6562
 6563           jk
 6564           nlmo
 6565           "#
 6566    ));
 6567
 6568    cx.update_editor(|editor, window, cx| {
 6569        editor.add_selection_below(&Default::default(), window, cx);
 6570    });
 6571
 6572    cx.assert_editor_state(indoc!(
 6573        r#"abc
 6574           defˇghi
 6575           ˇ
 6576           jk
 6577           nlmo
 6578           "#
 6579    ));
 6580
 6581    cx.update_editor(|editor, window, cx| {
 6582        editor.add_selection_below(&Default::default(), window, cx);
 6583    });
 6584
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abc
 6587           defˇghi
 6588           ˇ
 6589           jkˇ
 6590           nlmo
 6591           "#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.add_selection_below(&Default::default(), window, cx);
 6596    });
 6597
 6598    cx.assert_editor_state(indoc!(
 6599        r#"abc
 6600           defˇghi
 6601           ˇ
 6602           jkˇ
 6603           nlmˇo
 6604           "#
 6605    ));
 6606
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_below(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"abc
 6613           defˇghi
 6614           ˇ
 6615           jkˇ
 6616           nlmˇo
 6617           ˇ"#
 6618    ));
 6619
 6620    // change selections
 6621    cx.set_state(indoc!(
 6622        r#"abc
 6623           def«ˇg»hi
 6624
 6625           jk
 6626           nlmo
 6627           "#
 6628    ));
 6629
 6630    cx.update_editor(|editor, window, cx| {
 6631        editor.add_selection_below(&Default::default(), window, cx);
 6632    });
 6633
 6634    cx.assert_editor_state(indoc!(
 6635        r#"abc
 6636           def«ˇg»hi
 6637
 6638           jk
 6639           nlm«ˇo»
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"abc
 6649           def«ˇg»hi
 6650
 6651           jk
 6652           nlm«ˇo»
 6653           "#
 6654    ));
 6655
 6656    cx.update_editor(|editor, window, cx| {
 6657        editor.add_selection_above(&Default::default(), window, cx);
 6658    });
 6659
 6660    cx.assert_editor_state(indoc!(
 6661        r#"abc
 6662           def«ˇg»hi
 6663
 6664           jk
 6665           nlmo
 6666           "#
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.add_selection_above(&Default::default(), window, cx);
 6671    });
 6672
 6673    cx.assert_editor_state(indoc!(
 6674        r#"abc
 6675           def«ˇg»hi
 6676
 6677           jk
 6678           nlmo
 6679           "#
 6680    ));
 6681
 6682    // Change selections again
 6683    cx.set_state(indoc!(
 6684        r#"a«bc
 6685           defgˇ»hi
 6686
 6687           jk
 6688           nlmo
 6689           "#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_below(&Default::default(), window, cx);
 6694    });
 6695
 6696    cx.assert_editor_state(indoc!(
 6697        r#"a«bcˇ»
 6698           d«efgˇ»hi
 6699
 6700           j«kˇ»
 6701           nlmo
 6702           "#
 6703    ));
 6704
 6705    cx.update_editor(|editor, window, cx| {
 6706        editor.add_selection_below(&Default::default(), window, cx);
 6707    });
 6708    cx.assert_editor_state(indoc!(
 6709        r#"a«bcˇ»
 6710           d«efgˇ»hi
 6711
 6712           j«kˇ»
 6713           n«lmoˇ»
 6714           "#
 6715    ));
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_above(&Default::default(), window, cx);
 6718    });
 6719
 6720    cx.assert_editor_state(indoc!(
 6721        r#"a«bcˇ»
 6722           d«efgˇ»hi
 6723
 6724           j«kˇ»
 6725           nlmo
 6726           "#
 6727    ));
 6728
 6729    // Change selections again
 6730    cx.set_state(indoc!(
 6731        r#"abc
 6732           d«ˇefghi
 6733
 6734           jk
 6735           nlm»o
 6736           "#
 6737    ));
 6738
 6739    cx.update_editor(|editor, window, cx| {
 6740        editor.add_selection_above(&Default::default(), window, cx);
 6741    });
 6742
 6743    cx.assert_editor_state(indoc!(
 6744        r#"a«ˇbc»
 6745           d«ˇef»ghi
 6746
 6747           j«ˇk»
 6748           n«ˇlm»o
 6749           "#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.add_selection_below(&Default::default(), window, cx);
 6754    });
 6755
 6756    cx.assert_editor_state(indoc!(
 6757        r#"abc
 6758           d«ˇef»ghi
 6759
 6760           j«ˇk»
 6761           n«ˇlm»o
 6762           "#
 6763    ));
 6764}
 6765
 6766#[gpui::test]
 6767async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6768    init_test(cx, |_| {});
 6769    let mut cx = EditorTestContext::new(cx).await;
 6770
 6771    cx.set_state(indoc!(
 6772        r#"line onˇe
 6773           liˇne two
 6774           line three
 6775           line four"#
 6776    ));
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursors expand in the same direction
 6783    cx.assert_editor_state(indoc!(
 6784        r#"line onˇe
 6785           liˇne twˇo
 6786           liˇne three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.add_selection_below(&Default::default(), window, cx);
 6792    });
 6793
 6794    cx.update_editor(|editor, window, cx| {
 6795        editor.add_selection_below(&Default::default(), window, cx);
 6796    });
 6797
 6798    // test multiple cursors expand below overflow
 6799    cx.assert_editor_state(indoc!(
 6800        r#"line onˇe
 6801           liˇne twˇo
 6802           liˇne thˇree
 6803           liˇne foˇur"#
 6804    ));
 6805
 6806    cx.update_editor(|editor, window, cx| {
 6807        editor.add_selection_above(&Default::default(), window, cx);
 6808    });
 6809
 6810    // test multiple cursors retrieves back correctly
 6811    cx.assert_editor_state(indoc!(
 6812        r#"line onˇe
 6813           liˇne twˇo
 6814           liˇne thˇree
 6815           line four"#
 6816    ));
 6817
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.add_selection_above(&Default::default(), window, cx);
 6820    });
 6821
 6822    cx.update_editor(|editor, window, cx| {
 6823        editor.add_selection_above(&Default::default(), window, cx);
 6824    });
 6825
 6826    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6827    cx.assert_editor_state(indoc!(
 6828        r#"liˇne onˇe
 6829           liˇne two
 6830           line three
 6831           line four"#
 6832    ));
 6833
 6834    cx.update_editor(|editor, window, cx| {
 6835        editor.undo_selection(&Default::default(), window, cx);
 6836    });
 6837
 6838    // test undo
 6839    cx.assert_editor_state(indoc!(
 6840        r#"line onˇe
 6841           liˇne twˇo
 6842           line three
 6843           line four"#
 6844    ));
 6845
 6846    cx.update_editor(|editor, window, cx| {
 6847        editor.redo_selection(&Default::default(), window, cx);
 6848    });
 6849
 6850    // test redo
 6851    cx.assert_editor_state(indoc!(
 6852        r#"liˇne onˇe
 6853           liˇne two
 6854           line three
 6855           line four"#
 6856    ));
 6857
 6858    cx.set_state(indoc!(
 6859        r#"abcd
 6860           ef«ghˇ»
 6861           ijkl
 6862           «mˇ»nop"#
 6863    ));
 6864
 6865    cx.update_editor(|editor, window, cx| {
 6866        editor.add_selection_above(&Default::default(), window, cx);
 6867    });
 6868
 6869    // test multiple selections expand in the same direction
 6870    cx.assert_editor_state(indoc!(
 6871        r#"ab«cdˇ»
 6872           ef«ghˇ»
 6873           «iˇ»jkl
 6874           «mˇ»nop"#
 6875    ));
 6876
 6877    cx.update_editor(|editor, window, cx| {
 6878        editor.add_selection_above(&Default::default(), window, cx);
 6879    });
 6880
 6881    // test multiple selection upward overflow
 6882    cx.assert_editor_state(indoc!(
 6883        r#"ab«cdˇ»
 6884           «eˇ»f«ghˇ»
 6885           «iˇ»jkl
 6886           «mˇ»nop"#
 6887    ));
 6888
 6889    cx.update_editor(|editor, window, cx| {
 6890        editor.add_selection_below(&Default::default(), window, cx);
 6891    });
 6892
 6893    // test multiple selection retrieves back correctly
 6894    cx.assert_editor_state(indoc!(
 6895        r#"abcd
 6896           ef«ghˇ»
 6897           «iˇ»jkl
 6898           «mˇ»nop"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_below(&Default::default(), window, cx);
 6903    });
 6904
 6905    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6906    cx.assert_editor_state(indoc!(
 6907        r#"abcd
 6908           ef«ghˇ»
 6909           ij«klˇ»
 6910           «mˇ»nop"#
 6911    ));
 6912
 6913    cx.update_editor(|editor, window, cx| {
 6914        editor.undo_selection(&Default::default(), window, cx);
 6915    });
 6916
 6917    // test undo
 6918    cx.assert_editor_state(indoc!(
 6919        r#"abcd
 6920           ef«ghˇ»
 6921           «iˇ»jkl
 6922           «mˇ»nop"#
 6923    ));
 6924
 6925    cx.update_editor(|editor, window, cx| {
 6926        editor.redo_selection(&Default::default(), window, cx);
 6927    });
 6928
 6929    // test redo
 6930    cx.assert_editor_state(indoc!(
 6931        r#"abcd
 6932           ef«ghˇ»
 6933           ij«klˇ»
 6934           «mˇ»nop"#
 6935    ));
 6936}
 6937
 6938#[gpui::test]
 6939async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6940    init_test(cx, |_| {});
 6941    let mut cx = EditorTestContext::new(cx).await;
 6942
 6943    cx.set_state(indoc!(
 6944        r#"line onˇe
 6945           liˇne two
 6946           line three
 6947           line four"#
 6948    ));
 6949
 6950    cx.update_editor(|editor, window, cx| {
 6951        editor.add_selection_below(&Default::default(), window, cx);
 6952        editor.add_selection_below(&Default::default(), window, cx);
 6953        editor.add_selection_below(&Default::default(), window, cx);
 6954    });
 6955
 6956    // initial state with two multi cursor groups
 6957    cx.assert_editor_state(indoc!(
 6958        r#"line onˇe
 6959           liˇne twˇo
 6960           liˇne thˇree
 6961           liˇne foˇur"#
 6962    ));
 6963
 6964    // add single cursor in middle - simulate opt click
 6965    cx.update_editor(|editor, window, cx| {
 6966        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6967        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6968        editor.end_selection(window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"line onˇe
 6973           liˇne twˇo
 6974           liˇneˇ thˇree
 6975           liˇne foˇur"#
 6976    ));
 6977
 6978    cx.update_editor(|editor, window, cx| {
 6979        editor.add_selection_above(&Default::default(), window, cx);
 6980    });
 6981
 6982    // test new added selection expands above and existing selection shrinks
 6983    cx.assert_editor_state(indoc!(
 6984        r#"line onˇe
 6985           liˇneˇ twˇo
 6986           liˇneˇ thˇree
 6987           line four"#
 6988    ));
 6989
 6990    cx.update_editor(|editor, window, cx| {
 6991        editor.add_selection_above(&Default::default(), window, cx);
 6992    });
 6993
 6994    // test new added selection expands above and existing selection shrinks
 6995    cx.assert_editor_state(indoc!(
 6996        r#"lineˇ onˇe
 6997           liˇneˇ twˇo
 6998           lineˇ three
 6999           line four"#
 7000    ));
 7001
 7002    // intial state with two selection groups
 7003    cx.set_state(indoc!(
 7004        r#"abcd
 7005           ef«ghˇ»
 7006           ijkl
 7007           «mˇ»nop"#
 7008    ));
 7009
 7010    cx.update_editor(|editor, window, cx| {
 7011        editor.add_selection_above(&Default::default(), window, cx);
 7012        editor.add_selection_above(&Default::default(), window, cx);
 7013    });
 7014
 7015    cx.assert_editor_state(indoc!(
 7016        r#"ab«cdˇ»
 7017           «eˇ»f«ghˇ»
 7018           «iˇ»jkl
 7019           «mˇ»nop"#
 7020    ));
 7021
 7022    // add single selection in middle - simulate opt drag
 7023    cx.update_editor(|editor, window, cx| {
 7024        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7025        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7026        editor.update_selection(
 7027            DisplayPoint::new(DisplayRow(2), 4),
 7028            0,
 7029            gpui::Point::<f32>::default(),
 7030            window,
 7031            cx,
 7032        );
 7033        editor.end_selection(window, cx);
 7034    });
 7035
 7036    cx.assert_editor_state(indoc!(
 7037        r#"ab«cdˇ»
 7038           «eˇ»f«ghˇ»
 7039           «iˇ»jk«lˇ»
 7040           «mˇ»nop"#
 7041    ));
 7042
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.add_selection_below(&Default::default(), window, cx);
 7045    });
 7046
 7047    // test new added selection expands below, others shrinks from above
 7048    cx.assert_editor_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           «iˇ»jk«lˇ»
 7052           «mˇ»no«pˇ»"#
 7053    ));
 7054}
 7055
 7056#[gpui::test]
 7057async fn test_select_next(cx: &mut TestAppContext) {
 7058    init_test(cx, |_| {});
 7059
 7060    let mut cx = EditorTestContext::new(cx).await;
 7061    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7062
 7063    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7066
 7067    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7068        .unwrap();
 7069    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7070
 7071    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7072    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7073
 7074    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7075    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7076
 7077    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7078        .unwrap();
 7079    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7080
 7081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7084
 7085    // Test selection direction should be preserved
 7086    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7087
 7088    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7089        .unwrap();
 7090    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7091}
 7092
 7093#[gpui::test]
 7094async fn test_select_all_matches(cx: &mut TestAppContext) {
 7095    init_test(cx, |_| {});
 7096
 7097    let mut cx = EditorTestContext::new(cx).await;
 7098
 7099    // Test caret-only selections
 7100    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7101    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7102        .unwrap();
 7103    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7104
 7105    // Test left-to-right selections
 7106    cx.set_state("abc\n«abcˇ»\nabc");
 7107    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7108        .unwrap();
 7109    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7110
 7111    // Test right-to-left selections
 7112    cx.set_state("abc\n«ˇabc»\nabc");
 7113    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7114        .unwrap();
 7115    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7116
 7117    // Test selecting whitespace with caret selection
 7118    cx.set_state("abc\nˇ   abc\nabc");
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7122
 7123    // Test selecting whitespace with left-to-right selection
 7124    cx.set_state("abc\n«ˇ  »abc\nabc");
 7125    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7126        .unwrap();
 7127    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7128
 7129    // Test no matches with right-to-left selection
 7130    cx.set_state("abc\n«  ˇ»abc\nabc");
 7131    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7132        .unwrap();
 7133    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7134
 7135    // Test with a single word and clip_at_line_ends=true (#29823)
 7136    cx.set_state("aˇbc");
 7137    cx.update_editor(|e, window, cx| {
 7138        e.set_clip_at_line_ends(true, cx);
 7139        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7140        e.set_clip_at_line_ends(false, cx);
 7141    });
 7142    cx.assert_editor_state("«abcˇ»");
 7143}
 7144
 7145#[gpui::test]
 7146async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7147    init_test(cx, |_| {});
 7148
 7149    let mut cx = EditorTestContext::new(cx).await;
 7150
 7151    let large_body_1 = "\nd".repeat(200);
 7152    let large_body_2 = "\ne".repeat(200);
 7153
 7154    cx.set_state(&format!(
 7155        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7156    ));
 7157    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7158        let scroll_position = editor.scroll_position(cx);
 7159        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7160        scroll_position
 7161    });
 7162
 7163    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7164        .unwrap();
 7165    cx.assert_editor_state(&format!(
 7166        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7167    ));
 7168    let scroll_position_after_selection =
 7169        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7170    assert_eq!(
 7171        initial_scroll_position, scroll_position_after_selection,
 7172        "Scroll position should not change after selecting all matches"
 7173    );
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179
 7180    let mut cx = EditorLspTestContext::new_rust(
 7181        lsp::ServerCapabilities {
 7182            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7183            ..Default::default()
 7184        },
 7185        cx,
 7186    )
 7187    .await;
 7188
 7189    cx.set_state(indoc! {"
 7190        line 1
 7191        line 2
 7192        linˇe 3
 7193        line 4
 7194        line 5
 7195    "});
 7196
 7197    // Make an edit
 7198    cx.update_editor(|editor, window, cx| {
 7199        editor.handle_input("X", window, cx);
 7200    });
 7201
 7202    // Move cursor to a different position
 7203    cx.update_editor(|editor, window, cx| {
 7204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7205            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7206        });
 7207    });
 7208
 7209    cx.assert_editor_state(indoc! {"
 7210        line 1
 7211        line 2
 7212        linXe 3
 7213        line 4
 7214        liˇne 5
 7215    "});
 7216
 7217    cx.lsp
 7218        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7219            Ok(Some(vec![lsp::TextEdit::new(
 7220                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7221                "PREFIX ".to_string(),
 7222            )]))
 7223        });
 7224
 7225    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7226        .unwrap()
 7227        .await
 7228        .unwrap();
 7229
 7230    cx.assert_editor_state(indoc! {"
 7231        PREFIX line 1
 7232        line 2
 7233        linXe 3
 7234        line 4
 7235        liˇne 5
 7236    "});
 7237
 7238    // Undo formatting
 7239    cx.update_editor(|editor, window, cx| {
 7240        editor.undo(&Default::default(), window, cx);
 7241    });
 7242
 7243    // Verify cursor moved back to position after edit
 7244    cx.assert_editor_state(indoc! {"
 7245        line 1
 7246        line 2
 7247        linXˇe 3
 7248        line 4
 7249        line 5
 7250    "});
 7251}
 7252
 7253#[gpui::test]
 7254async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7255    init_test(cx, |_| {});
 7256
 7257    let mut cx = EditorTestContext::new(cx).await;
 7258
 7259    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7260    cx.update_editor(|editor, window, cx| {
 7261        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7262    });
 7263
 7264    cx.set_state(indoc! {"
 7265        line 1
 7266        line 2
 7267        linˇe 3
 7268        line 4
 7269        line 5
 7270        line 6
 7271        line 7
 7272        line 8
 7273        line 9
 7274        line 10
 7275    "});
 7276
 7277    let snapshot = cx.buffer_snapshot();
 7278    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7279
 7280    cx.update(|_, cx| {
 7281        provider.update(cx, |provider, _| {
 7282            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7283                id: None,
 7284                edits: vec![(edit_position..edit_position, "X".into())],
 7285                edit_preview: None,
 7286            }))
 7287        })
 7288    });
 7289
 7290    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7291    cx.update_editor(|editor, window, cx| {
 7292        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7293    });
 7294
 7295    cx.assert_editor_state(indoc! {"
 7296        line 1
 7297        line 2
 7298        lineXˇ 3
 7299        line 4
 7300        line 5
 7301        line 6
 7302        line 7
 7303        line 8
 7304        line 9
 7305        line 10
 7306    "});
 7307
 7308    cx.update_editor(|editor, window, cx| {
 7309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7310            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7311        });
 7312    });
 7313
 7314    cx.assert_editor_state(indoc! {"
 7315        line 1
 7316        line 2
 7317        lineX 3
 7318        line 4
 7319        line 5
 7320        line 6
 7321        line 7
 7322        line 8
 7323        line 9
 7324        liˇne 10
 7325    "});
 7326
 7327    cx.update_editor(|editor, window, cx| {
 7328        editor.undo(&Default::default(), window, cx);
 7329    });
 7330
 7331    cx.assert_editor_state(indoc! {"
 7332        line 1
 7333        line 2
 7334        lineˇ 3
 7335        line 4
 7336        line 5
 7337        line 6
 7338        line 7
 7339        line 8
 7340        line 9
 7341        line 10
 7342    "});
 7343}
 7344
 7345#[gpui::test]
 7346async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7347    init_test(cx, |_| {});
 7348
 7349    let mut cx = EditorTestContext::new(cx).await;
 7350    cx.set_state(
 7351        r#"let foo = 2;
 7352lˇet foo = 2;
 7353let fooˇ = 2;
 7354let foo = 2;
 7355let foo = ˇ2;"#,
 7356    );
 7357
 7358    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7359        .unwrap();
 7360    cx.assert_editor_state(
 7361        r#"let foo = 2;
 7362«letˇ» foo = 2;
 7363let «fooˇ» = 2;
 7364let foo = 2;
 7365let foo = «2ˇ»;"#,
 7366    );
 7367
 7368    // noop for multiple selections with different contents
 7369    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7370        .unwrap();
 7371    cx.assert_editor_state(
 7372        r#"let foo = 2;
 7373«letˇ» foo = 2;
 7374let «fooˇ» = 2;
 7375let foo = 2;
 7376let foo = «2ˇ»;"#,
 7377    );
 7378
 7379    // Test last selection direction should be preserved
 7380    cx.set_state(
 7381        r#"let foo = 2;
 7382let foo = 2;
 7383let «fooˇ» = 2;
 7384let «ˇfoo» = 2;
 7385let foo = 2;"#,
 7386    );
 7387
 7388    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7389        .unwrap();
 7390    cx.assert_editor_state(
 7391        r#"let foo = 2;
 7392let foo = 2;
 7393let «fooˇ» = 2;
 7394let «ˇfoo» = 2;
 7395let «ˇfoo» = 2;"#,
 7396    );
 7397}
 7398
 7399#[gpui::test]
 7400async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7401    init_test(cx, |_| {});
 7402
 7403    let mut cx =
 7404        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7405
 7406    cx.assert_editor_state(indoc! {"
 7407        ˇbbb
 7408        ccc
 7409
 7410        bbb
 7411        ccc
 7412        "});
 7413    cx.dispatch_action(SelectPrevious::default());
 7414    cx.assert_editor_state(indoc! {"
 7415                «bbbˇ»
 7416                ccc
 7417
 7418                bbb
 7419                ccc
 7420                "});
 7421    cx.dispatch_action(SelectPrevious::default());
 7422    cx.assert_editor_state(indoc! {"
 7423                «bbbˇ»
 7424                ccc
 7425
 7426                «bbbˇ»
 7427                ccc
 7428                "});
 7429}
 7430
 7431#[gpui::test]
 7432async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7433    init_test(cx, |_| {});
 7434
 7435    let mut cx = EditorTestContext::new(cx).await;
 7436    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7437
 7438    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7439        .unwrap();
 7440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7441
 7442    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7443        .unwrap();
 7444    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7445
 7446    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7447    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7448
 7449    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7450    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7451
 7452    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7453        .unwrap();
 7454    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7455
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7459}
 7460
 7461#[gpui::test]
 7462async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7463    init_test(cx, |_| {});
 7464
 7465    let mut cx = EditorTestContext::new(cx).await;
 7466    cx.set_state("");
 7467
 7468    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7469        .unwrap();
 7470    cx.assert_editor_state("«aˇ»");
 7471    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7472        .unwrap();
 7473    cx.assert_editor_state("«aˇ»");
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state(
 7482        r#"let foo = 2;
 7483lˇet foo = 2;
 7484let fooˇ = 2;
 7485let foo = 2;
 7486let foo = ˇ2;"#,
 7487    );
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state(
 7492        r#"let foo = 2;
 7493«letˇ» foo = 2;
 7494let «fooˇ» = 2;
 7495let foo = 2;
 7496let foo = «2ˇ»;"#,
 7497    );
 7498
 7499    // noop for multiple selections with different contents
 7500    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7501        .unwrap();
 7502    cx.assert_editor_state(
 7503        r#"let foo = 2;
 7504«letˇ» foo = 2;
 7505let «fooˇ» = 2;
 7506let foo = 2;
 7507let foo = «2ˇ»;"#,
 7508    );
 7509}
 7510
 7511#[gpui::test]
 7512async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7513    init_test(cx, |_| {});
 7514
 7515    let mut cx = EditorTestContext::new(cx).await;
 7516    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7517
 7518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7519        .unwrap();
 7520    // selection direction is preserved
 7521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7522
 7523    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7524        .unwrap();
 7525    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7526
 7527    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7529
 7530    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7531    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7532
 7533    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7534        .unwrap();
 7535    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7536
 7537    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7538        .unwrap();
 7539    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7540}
 7541
 7542#[gpui::test]
 7543async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7544    init_test(cx, |_| {});
 7545
 7546    let language = Arc::new(Language::new(
 7547        LanguageConfig::default(),
 7548        Some(tree_sitter_rust::LANGUAGE.into()),
 7549    ));
 7550
 7551    let text = r#"
 7552        use mod1::mod2::{mod3, mod4};
 7553
 7554        fn fn_1(param1: bool, param2: &str) {
 7555            let var1 = "text";
 7556        }
 7557    "#
 7558    .unindent();
 7559
 7560    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7561    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7562    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7563
 7564    editor
 7565        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7566        .await;
 7567
 7568    editor.update_in(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7572                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7573                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7574            ]);
 7575        });
 7576        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7577    });
 7578    editor.update(cx, |editor, cx| {
 7579        assert_text_with_selections(
 7580            editor,
 7581            indoc! {r#"
 7582                use mod1::mod2::{mod3, «mod4ˇ»};
 7583
 7584                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7585                    let var1 = "«ˇtext»";
 7586                }
 7587            "#},
 7588            cx,
 7589        );
 7590    });
 7591
 7592    editor.update_in(cx, |editor, window, cx| {
 7593        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7594    });
 7595    editor.update(cx, |editor, cx| {
 7596        assert_text_with_selections(
 7597            editor,
 7598            indoc! {r#"
 7599                use mod1::mod2::«{mod3, mod4}ˇ»;
 7600
 7601                «ˇfn fn_1(param1: bool, param2: &str) {
 7602                    let var1 = "text";
 7603 7604            "#},
 7605            cx,
 7606        );
 7607    });
 7608
 7609    editor.update_in(cx, |editor, window, cx| {
 7610        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7611    });
 7612    assert_eq!(
 7613        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7614        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7615    );
 7616
 7617    // Trying to expand the selected syntax node one more time has no effect.
 7618    editor.update_in(cx, |editor, window, cx| {
 7619        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7620    });
 7621    assert_eq!(
 7622        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7623        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7624    );
 7625
 7626    editor.update_in(cx, |editor, window, cx| {
 7627        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7628    });
 7629    editor.update(cx, |editor, cx| {
 7630        assert_text_with_selections(
 7631            editor,
 7632            indoc! {r#"
 7633                use mod1::mod2::«{mod3, mod4}ˇ»;
 7634
 7635                «ˇfn fn_1(param1: bool, param2: &str) {
 7636                    let var1 = "text";
 7637 7638            "#},
 7639            cx,
 7640        );
 7641    });
 7642
 7643    editor.update_in(cx, |editor, window, cx| {
 7644        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7645    });
 7646    editor.update(cx, |editor, cx| {
 7647        assert_text_with_selections(
 7648            editor,
 7649            indoc! {r#"
 7650                use mod1::mod2::{mod3, «mod4ˇ»};
 7651
 7652                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7653                    let var1 = "«ˇtext»";
 7654                }
 7655            "#},
 7656            cx,
 7657        );
 7658    });
 7659
 7660    editor.update_in(cx, |editor, window, cx| {
 7661        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7662    });
 7663    editor.update(cx, |editor, cx| {
 7664        assert_text_with_selections(
 7665            editor,
 7666            indoc! {r#"
 7667                use mod1::mod2::{mod3, mo«ˇ»d4};
 7668
 7669                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7670                    let var1 = "te«ˇ»xt";
 7671                }
 7672            "#},
 7673            cx,
 7674        );
 7675    });
 7676
 7677    // Trying to shrink the selected syntax node one more time has no effect.
 7678    editor.update_in(cx, |editor, window, cx| {
 7679        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7680    });
 7681    editor.update_in(cx, |editor, _, cx| {
 7682        assert_text_with_selections(
 7683            editor,
 7684            indoc! {r#"
 7685                use mod1::mod2::{mod3, mo«ˇ»d4};
 7686
 7687                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7688                    let var1 = "te«ˇ»xt";
 7689                }
 7690            "#},
 7691            cx,
 7692        );
 7693    });
 7694
 7695    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7696    // a fold.
 7697    editor.update_in(cx, |editor, window, cx| {
 7698        editor.fold_creases(
 7699            vec![
 7700                Crease::simple(
 7701                    Point::new(0, 21)..Point::new(0, 24),
 7702                    FoldPlaceholder::test(),
 7703                ),
 7704                Crease::simple(
 7705                    Point::new(3, 20)..Point::new(3, 22),
 7706                    FoldPlaceholder::test(),
 7707                ),
 7708            ],
 7709            true,
 7710            window,
 7711            cx,
 7712        );
 7713        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7714    });
 7715    editor.update(cx, |editor, cx| {
 7716        assert_text_with_selections(
 7717            editor,
 7718            indoc! {r#"
 7719                use mod1::mod2::«{mod3, mod4}ˇ»;
 7720
 7721                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7722                    let var1 = "«ˇtext»";
 7723                }
 7724            "#},
 7725            cx,
 7726        );
 7727    });
 7728}
 7729
 7730#[gpui::test]
 7731async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7732    init_test(cx, |_| {});
 7733
 7734    let language = Arc::new(Language::new(
 7735        LanguageConfig::default(),
 7736        Some(tree_sitter_rust::LANGUAGE.into()),
 7737    ));
 7738
 7739    let text = "let a = 2;";
 7740
 7741    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7742    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7743    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7744
 7745    editor
 7746        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7747        .await;
 7748
 7749    // Test case 1: Cursor at end of word
 7750    editor.update_in(cx, |editor, window, cx| {
 7751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7752            s.select_display_ranges([
 7753                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7754            ]);
 7755        });
 7756    });
 7757    editor.update(cx, |editor, cx| {
 7758        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7759    });
 7760    editor.update_in(cx, |editor, window, cx| {
 7761        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7762    });
 7763    editor.update(cx, |editor, cx| {
 7764        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7765    });
 7766    editor.update_in(cx, |editor, window, cx| {
 7767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7768    });
 7769    editor.update(cx, |editor, cx| {
 7770        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7771    });
 7772
 7773    // Test case 2: Cursor at end of statement
 7774    editor.update_in(cx, |editor, window, cx| {
 7775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7776            s.select_display_ranges([
 7777                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7778            ]);
 7779        });
 7780    });
 7781    editor.update(cx, |editor, cx| {
 7782        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7783    });
 7784    editor.update_in(cx, |editor, window, cx| {
 7785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7786    });
 7787    editor.update(cx, |editor, cx| {
 7788        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7789    });
 7790}
 7791
 7792#[gpui::test]
 7793async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7794    init_test(cx, |_| {});
 7795
 7796    let language = Arc::new(Language::new(
 7797        LanguageConfig::default(),
 7798        Some(tree_sitter_rust::LANGUAGE.into()),
 7799    ));
 7800
 7801    let text = r#"
 7802        use mod1::mod2::{mod3, mod4};
 7803
 7804        fn fn_1(param1: bool, param2: &str) {
 7805            let var1 = "hello world";
 7806        }
 7807    "#
 7808    .unindent();
 7809
 7810    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7811    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7812    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7813
 7814    editor
 7815        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7816        .await;
 7817
 7818    // Test 1: Cursor on a letter of a string word
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update_in(cx, |editor, window, cx| {
 7827        assert_text_with_selections(
 7828            editor,
 7829            indoc! {r#"
 7830                use mod1::mod2::{mod3, mod4};
 7831
 7832                fn fn_1(param1: bool, param2: &str) {
 7833                    let var1 = "hˇello world";
 7834                }
 7835            "#},
 7836            cx,
 7837        );
 7838        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7839        assert_text_with_selections(
 7840            editor,
 7841            indoc! {r#"
 7842                use mod1::mod2::{mod3, mod4};
 7843
 7844                fn fn_1(param1: bool, param2: &str) {
 7845                    let var1 = "«ˇhello» world";
 7846                }
 7847            "#},
 7848            cx,
 7849        );
 7850    });
 7851
 7852    // Test 2: Partial selection within a word
 7853    editor.update_in(cx, |editor, window, cx| {
 7854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7855            s.select_display_ranges([
 7856                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7857            ]);
 7858        });
 7859    });
 7860    editor.update_in(cx, |editor, window, cx| {
 7861        assert_text_with_selections(
 7862            editor,
 7863            indoc! {r#"
 7864                use mod1::mod2::{mod3, mod4};
 7865
 7866                fn fn_1(param1: bool, param2: &str) {
 7867                    let var1 = "h«elˇ»lo world";
 7868                }
 7869            "#},
 7870            cx,
 7871        );
 7872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7873        assert_text_with_selections(
 7874            editor,
 7875            indoc! {r#"
 7876                use mod1::mod2::{mod3, mod4};
 7877
 7878                fn fn_1(param1: bool, param2: &str) {
 7879                    let var1 = "«ˇhello» world";
 7880                }
 7881            "#},
 7882            cx,
 7883        );
 7884    });
 7885
 7886    // Test 3: Complete word already selected
 7887    editor.update_in(cx, |editor, window, cx| {
 7888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7889            s.select_display_ranges([
 7890                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7891            ]);
 7892        });
 7893    });
 7894    editor.update_in(cx, |editor, window, cx| {
 7895        assert_text_with_selections(
 7896            editor,
 7897            indoc! {r#"
 7898                use mod1::mod2::{mod3, mod4};
 7899
 7900                fn fn_1(param1: bool, param2: &str) {
 7901                    let var1 = "«helloˇ» world";
 7902                }
 7903            "#},
 7904            cx,
 7905        );
 7906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7907        assert_text_with_selections(
 7908            editor,
 7909            indoc! {r#"
 7910                use mod1::mod2::{mod3, mod4};
 7911
 7912                fn fn_1(param1: bool, param2: &str) {
 7913                    let var1 = "«hello worldˇ»";
 7914                }
 7915            "#},
 7916            cx,
 7917        );
 7918    });
 7919
 7920    // Test 4: Selection spanning across words
 7921    editor.update_in(cx, |editor, window, cx| {
 7922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7923            s.select_display_ranges([
 7924                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7925            ]);
 7926        });
 7927    });
 7928    editor.update_in(cx, |editor, window, cx| {
 7929        assert_text_with_selections(
 7930            editor,
 7931            indoc! {r#"
 7932                use mod1::mod2::{mod3, mod4};
 7933
 7934                fn fn_1(param1: bool, param2: &str) {
 7935                    let var1 = "hel«lo woˇ»rld";
 7936                }
 7937            "#},
 7938            cx,
 7939        );
 7940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7941        assert_text_with_selections(
 7942            editor,
 7943            indoc! {r#"
 7944                use mod1::mod2::{mod3, mod4};
 7945
 7946                fn fn_1(param1: bool, param2: &str) {
 7947                    let var1 = "«ˇhello world»";
 7948                }
 7949            "#},
 7950            cx,
 7951        );
 7952    });
 7953
 7954    // Test 5: Expansion beyond string
 7955    editor.update_in(cx, |editor, window, cx| {
 7956        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7957        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7958        assert_text_with_selections(
 7959            editor,
 7960            indoc! {r#"
 7961                use mod1::mod2::{mod3, mod4};
 7962
 7963                fn fn_1(param1: bool, param2: &str) {
 7964                    «ˇlet var1 = "hello world";»
 7965                }
 7966            "#},
 7967            cx,
 7968        );
 7969    });
 7970}
 7971
 7972#[gpui::test]
 7973async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7974    init_test(cx, |_| {});
 7975
 7976    let base_text = r#"
 7977        impl A {
 7978            // this is an uncommitted comment
 7979
 7980            fn b() {
 7981                c();
 7982            }
 7983
 7984            // this is another uncommitted comment
 7985
 7986            fn d() {
 7987                // e
 7988                // f
 7989            }
 7990        }
 7991
 7992        fn g() {
 7993            // h
 7994        }
 7995    "#
 7996    .unindent();
 7997
 7998    let text = r#"
 7999        ˇimpl A {
 8000
 8001            fn b() {
 8002                c();
 8003            }
 8004
 8005            fn d() {
 8006                // e
 8007                // f
 8008            }
 8009        }
 8010
 8011        fn g() {
 8012            // h
 8013        }
 8014    "#
 8015    .unindent();
 8016
 8017    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8018    cx.set_state(&text);
 8019    cx.set_head_text(&base_text);
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8022    });
 8023
 8024    cx.assert_state_with_diff(
 8025        "
 8026        ˇimpl A {
 8027      -     // this is an uncommitted comment
 8028
 8029            fn b() {
 8030                c();
 8031            }
 8032
 8033      -     // this is another uncommitted comment
 8034      -
 8035            fn d() {
 8036                // e
 8037                // f
 8038            }
 8039        }
 8040
 8041        fn g() {
 8042            // h
 8043        }
 8044    "
 8045        .unindent(),
 8046    );
 8047
 8048    let expected_display_text = "
 8049        impl A {
 8050            // this is an uncommitted comment
 8051
 8052            fn b() {
 8053 8054            }
 8055
 8056            // this is another uncommitted comment
 8057
 8058            fn d() {
 8059 8060            }
 8061        }
 8062
 8063        fn g() {
 8064 8065        }
 8066        "
 8067    .unindent();
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8071        assert_eq!(editor.display_text(cx), expected_display_text);
 8072    });
 8073}
 8074
 8075#[gpui::test]
 8076async fn test_autoindent(cx: &mut TestAppContext) {
 8077    init_test(cx, |_| {});
 8078
 8079    let language = Arc::new(
 8080        Language::new(
 8081            LanguageConfig {
 8082                brackets: BracketPairConfig {
 8083                    pairs: vec![
 8084                        BracketPair {
 8085                            start: "{".to_string(),
 8086                            end: "}".to_string(),
 8087                            close: false,
 8088                            surround: false,
 8089                            newline: true,
 8090                        },
 8091                        BracketPair {
 8092                            start: "(".to_string(),
 8093                            end: ")".to_string(),
 8094                            close: false,
 8095                            surround: false,
 8096                            newline: true,
 8097                        },
 8098                    ],
 8099                    ..Default::default()
 8100                },
 8101                ..Default::default()
 8102            },
 8103            Some(tree_sitter_rust::LANGUAGE.into()),
 8104        )
 8105        .with_indents_query(
 8106            r#"
 8107                (_ "(" ")" @end) @indent
 8108                (_ "{" "}" @end) @indent
 8109            "#,
 8110        )
 8111        .unwrap(),
 8112    );
 8113
 8114    let text = "fn a() {}";
 8115
 8116    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8117    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8118    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8119    editor
 8120        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8121        .await;
 8122
 8123    editor.update_in(cx, |editor, window, cx| {
 8124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8125            s.select_ranges([5..5, 8..8, 9..9])
 8126        });
 8127        editor.newline(&Newline, window, cx);
 8128        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8129        assert_eq!(
 8130            editor.selections.ranges(cx),
 8131            &[
 8132                Point::new(1, 4)..Point::new(1, 4),
 8133                Point::new(3, 4)..Point::new(3, 4),
 8134                Point::new(5, 0)..Point::new(5, 0)
 8135            ]
 8136        );
 8137    });
 8138}
 8139
 8140#[gpui::test]
 8141async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8142    init_test(cx, |_| {});
 8143
 8144    {
 8145        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8146        cx.set_state(indoc! {"
 8147            impl A {
 8148
 8149                fn b() {}
 8150
 8151            «fn c() {
 8152
 8153            }ˇ»
 8154            }
 8155        "});
 8156
 8157        cx.update_editor(|editor, window, cx| {
 8158            editor.autoindent(&Default::default(), window, cx);
 8159        });
 8160
 8161        cx.assert_editor_state(indoc! {"
 8162            impl A {
 8163
 8164                fn b() {}
 8165
 8166                «fn c() {
 8167
 8168                }ˇ»
 8169            }
 8170        "});
 8171    }
 8172
 8173    {
 8174        let mut cx = EditorTestContext::new_multibuffer(
 8175            cx,
 8176            [indoc! { "
 8177                impl A {
 8178                «
 8179                // a
 8180                fn b(){}
 8181                »
 8182                «
 8183                    }
 8184                    fn c(){}
 8185                »
 8186            "}],
 8187        );
 8188
 8189        let buffer = cx.update_editor(|editor, _, cx| {
 8190            let buffer = editor.buffer().update(cx, |buffer, _| {
 8191                buffer.all_buffers().iter().next().unwrap().clone()
 8192            });
 8193            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8194            buffer
 8195        });
 8196
 8197        cx.run_until_parked();
 8198        cx.update_editor(|editor, window, cx| {
 8199            editor.select_all(&Default::default(), window, cx);
 8200            editor.autoindent(&Default::default(), window, cx)
 8201        });
 8202        cx.run_until_parked();
 8203
 8204        cx.update(|_, cx| {
 8205            assert_eq!(
 8206                buffer.read(cx).text(),
 8207                indoc! { "
 8208                    impl A {
 8209
 8210                        // a
 8211                        fn b(){}
 8212
 8213
 8214                    }
 8215                    fn c(){}
 8216
 8217                " }
 8218            )
 8219        });
 8220    }
 8221}
 8222
 8223#[gpui::test]
 8224async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8225    init_test(cx, |_| {});
 8226
 8227    let mut cx = EditorTestContext::new(cx).await;
 8228
 8229    let language = Arc::new(Language::new(
 8230        LanguageConfig {
 8231            brackets: BracketPairConfig {
 8232                pairs: vec![
 8233                    BracketPair {
 8234                        start: "{".to_string(),
 8235                        end: "}".to_string(),
 8236                        close: true,
 8237                        surround: true,
 8238                        newline: true,
 8239                    },
 8240                    BracketPair {
 8241                        start: "(".to_string(),
 8242                        end: ")".to_string(),
 8243                        close: true,
 8244                        surround: true,
 8245                        newline: true,
 8246                    },
 8247                    BracketPair {
 8248                        start: "/*".to_string(),
 8249                        end: " */".to_string(),
 8250                        close: true,
 8251                        surround: true,
 8252                        newline: true,
 8253                    },
 8254                    BracketPair {
 8255                        start: "[".to_string(),
 8256                        end: "]".to_string(),
 8257                        close: false,
 8258                        surround: false,
 8259                        newline: true,
 8260                    },
 8261                    BracketPair {
 8262                        start: "\"".to_string(),
 8263                        end: "\"".to_string(),
 8264                        close: true,
 8265                        surround: true,
 8266                        newline: false,
 8267                    },
 8268                    BracketPair {
 8269                        start: "<".to_string(),
 8270                        end: ">".to_string(),
 8271                        close: false,
 8272                        surround: true,
 8273                        newline: true,
 8274                    },
 8275                ],
 8276                ..Default::default()
 8277            },
 8278            autoclose_before: "})]".to_string(),
 8279            ..Default::default()
 8280        },
 8281        Some(tree_sitter_rust::LANGUAGE.into()),
 8282    ));
 8283
 8284    cx.language_registry().add(language.clone());
 8285    cx.update_buffer(|buffer, cx| {
 8286        buffer.set_language(Some(language), cx);
 8287    });
 8288
 8289    cx.set_state(
 8290        &r#"
 8291            🏀ˇ
 8292            εˇ
 8293            ❤️ˇ
 8294        "#
 8295        .unindent(),
 8296    );
 8297
 8298    // autoclose multiple nested brackets at multiple cursors
 8299    cx.update_editor(|editor, window, cx| {
 8300        editor.handle_input("{", window, cx);
 8301        editor.handle_input("{", window, cx);
 8302        editor.handle_input("{", window, cx);
 8303    });
 8304    cx.assert_editor_state(
 8305        &"
 8306            🏀{{{ˇ}}}
 8307            ε{{{ˇ}}}
 8308            ❤️{{{ˇ}}}
 8309        "
 8310        .unindent(),
 8311    );
 8312
 8313    // insert a different closing bracket
 8314    cx.update_editor(|editor, window, cx| {
 8315        editor.handle_input(")", window, cx);
 8316    });
 8317    cx.assert_editor_state(
 8318        &"
 8319            🏀{{{)ˇ}}}
 8320            ε{{{)ˇ}}}
 8321            ❤️{{{)ˇ}}}
 8322        "
 8323        .unindent(),
 8324    );
 8325
 8326    // skip over the auto-closed brackets when typing a closing bracket
 8327    cx.update_editor(|editor, window, cx| {
 8328        editor.move_right(&MoveRight, window, cx);
 8329        editor.handle_input("}", window, cx);
 8330        editor.handle_input("}", window, cx);
 8331        editor.handle_input("}", window, cx);
 8332    });
 8333    cx.assert_editor_state(
 8334        &"
 8335            🏀{{{)}}}}ˇ
 8336            ε{{{)}}}}ˇ
 8337            ❤️{{{)}}}}ˇ
 8338        "
 8339        .unindent(),
 8340    );
 8341
 8342    // autoclose multi-character pairs
 8343    cx.set_state(
 8344        &"
 8345            ˇ
 8346            ˇ
 8347        "
 8348        .unindent(),
 8349    );
 8350    cx.update_editor(|editor, window, cx| {
 8351        editor.handle_input("/", window, cx);
 8352        editor.handle_input("*", window, cx);
 8353    });
 8354    cx.assert_editor_state(
 8355        &"
 8356            /*ˇ */
 8357            /*ˇ */
 8358        "
 8359        .unindent(),
 8360    );
 8361
 8362    // one cursor autocloses a multi-character pair, one cursor
 8363    // does not autoclose.
 8364    cx.set_state(
 8365        &"
 8366 8367            ˇ
 8368        "
 8369        .unindent(),
 8370    );
 8371    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8372    cx.assert_editor_state(
 8373        &"
 8374            /*ˇ */
 8375 8376        "
 8377        .unindent(),
 8378    );
 8379
 8380    // Don't autoclose if the next character isn't whitespace and isn't
 8381    // listed in the language's "autoclose_before" section.
 8382    cx.set_state("ˇa b");
 8383    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8384    cx.assert_editor_state("{ˇa b");
 8385
 8386    // Don't autoclose if `close` is false for the bracket pair
 8387    cx.set_state("ˇ");
 8388    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8389    cx.assert_editor_state("");
 8390
 8391    // Surround with brackets if text is selected
 8392    cx.set_state("«aˇ» b");
 8393    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8394    cx.assert_editor_state("{«aˇ»} b");
 8395
 8396    // Autoclose when not immediately after a word character
 8397    cx.set_state("a ˇ");
 8398    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8399    cx.assert_editor_state("a \"ˇ\"");
 8400
 8401    // Autoclose pair where the start and end characters are the same
 8402    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8403    cx.assert_editor_state("a \"\"ˇ");
 8404
 8405    // Don't autoclose when immediately after a word character
 8406    cx.set_state("");
 8407    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8408    cx.assert_editor_state("a\"ˇ");
 8409
 8410    // Do autoclose when after a non-word character
 8411    cx.set_state("");
 8412    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8413    cx.assert_editor_state("{\"ˇ\"");
 8414
 8415    // Non identical pairs autoclose regardless of preceding character
 8416    cx.set_state("");
 8417    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8418    cx.assert_editor_state("a{ˇ}");
 8419
 8420    // Don't autoclose pair if autoclose is disabled
 8421    cx.set_state("ˇ");
 8422    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8423    cx.assert_editor_state("");
 8424
 8425    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8426    cx.set_state("«aˇ» b");
 8427    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8428    cx.assert_editor_state("<«aˇ»> b");
 8429}
 8430
 8431#[gpui::test]
 8432async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8433    init_test(cx, |settings| {
 8434        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8435    });
 8436
 8437    let mut cx = EditorTestContext::new(cx).await;
 8438
 8439    let language = Arc::new(Language::new(
 8440        LanguageConfig {
 8441            brackets: BracketPairConfig {
 8442                pairs: vec![
 8443                    BracketPair {
 8444                        start: "{".to_string(),
 8445                        end: "}".to_string(),
 8446                        close: true,
 8447                        surround: true,
 8448                        newline: true,
 8449                    },
 8450                    BracketPair {
 8451                        start: "(".to_string(),
 8452                        end: ")".to_string(),
 8453                        close: true,
 8454                        surround: true,
 8455                        newline: true,
 8456                    },
 8457                    BracketPair {
 8458                        start: "[".to_string(),
 8459                        end: "]".to_string(),
 8460                        close: false,
 8461                        surround: false,
 8462                        newline: true,
 8463                    },
 8464                ],
 8465                ..Default::default()
 8466            },
 8467            autoclose_before: "})]".to_string(),
 8468            ..Default::default()
 8469        },
 8470        Some(tree_sitter_rust::LANGUAGE.into()),
 8471    ));
 8472
 8473    cx.language_registry().add(language.clone());
 8474    cx.update_buffer(|buffer, cx| {
 8475        buffer.set_language(Some(language), cx);
 8476    });
 8477
 8478    cx.set_state(
 8479        &"
 8480            ˇ
 8481            ˇ
 8482            ˇ
 8483        "
 8484        .unindent(),
 8485    );
 8486
 8487    // ensure only matching closing brackets are skipped over
 8488    cx.update_editor(|editor, window, cx| {
 8489        editor.handle_input("}", window, cx);
 8490        editor.move_left(&MoveLeft, window, cx);
 8491        editor.handle_input(")", window, cx);
 8492        editor.move_left(&MoveLeft, window, cx);
 8493    });
 8494    cx.assert_editor_state(
 8495        &"
 8496            ˇ)}
 8497            ˇ)}
 8498            ˇ)}
 8499        "
 8500        .unindent(),
 8501    );
 8502
 8503    // skip-over closing brackets at multiple cursors
 8504    cx.update_editor(|editor, window, cx| {
 8505        editor.handle_input(")", window, cx);
 8506        editor.handle_input("}", window, cx);
 8507    });
 8508    cx.assert_editor_state(
 8509        &"
 8510            )}ˇ
 8511            )}ˇ
 8512            )}ˇ
 8513        "
 8514        .unindent(),
 8515    );
 8516
 8517    // ignore non-close brackets
 8518    cx.update_editor(|editor, window, cx| {
 8519        editor.handle_input("]", window, cx);
 8520        editor.move_left(&MoveLeft, window, cx);
 8521        editor.handle_input("]", window, cx);
 8522    });
 8523    cx.assert_editor_state(
 8524        &"
 8525            )}]ˇ]
 8526            )}]ˇ]
 8527            )}]ˇ]
 8528        "
 8529        .unindent(),
 8530    );
 8531}
 8532
 8533#[gpui::test]
 8534async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8535    init_test(cx, |_| {});
 8536
 8537    let mut cx = EditorTestContext::new(cx).await;
 8538
 8539    let html_language = Arc::new(
 8540        Language::new(
 8541            LanguageConfig {
 8542                name: "HTML".into(),
 8543                brackets: BracketPairConfig {
 8544                    pairs: vec![
 8545                        BracketPair {
 8546                            start: "<".into(),
 8547                            end: ">".into(),
 8548                            close: true,
 8549                            ..Default::default()
 8550                        },
 8551                        BracketPair {
 8552                            start: "{".into(),
 8553                            end: "}".into(),
 8554                            close: true,
 8555                            ..Default::default()
 8556                        },
 8557                        BracketPair {
 8558                            start: "(".into(),
 8559                            end: ")".into(),
 8560                            close: true,
 8561                            ..Default::default()
 8562                        },
 8563                    ],
 8564                    ..Default::default()
 8565                },
 8566                autoclose_before: "})]>".into(),
 8567                ..Default::default()
 8568            },
 8569            Some(tree_sitter_html::LANGUAGE.into()),
 8570        )
 8571        .with_injection_query(
 8572            r#"
 8573            (script_element
 8574                (raw_text) @injection.content
 8575                (#set! injection.language "javascript"))
 8576            "#,
 8577        )
 8578        .unwrap(),
 8579    );
 8580
 8581    let javascript_language = Arc::new(Language::new(
 8582        LanguageConfig {
 8583            name: "JavaScript".into(),
 8584            brackets: BracketPairConfig {
 8585                pairs: vec![
 8586                    BracketPair {
 8587                        start: "/*".into(),
 8588                        end: " */".into(),
 8589                        close: true,
 8590                        ..Default::default()
 8591                    },
 8592                    BracketPair {
 8593                        start: "{".into(),
 8594                        end: "}".into(),
 8595                        close: true,
 8596                        ..Default::default()
 8597                    },
 8598                    BracketPair {
 8599                        start: "(".into(),
 8600                        end: ")".into(),
 8601                        close: true,
 8602                        ..Default::default()
 8603                    },
 8604                ],
 8605                ..Default::default()
 8606            },
 8607            autoclose_before: "})]>".into(),
 8608            ..Default::default()
 8609        },
 8610        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8611    ));
 8612
 8613    cx.language_registry().add(html_language.clone());
 8614    cx.language_registry().add(javascript_language.clone());
 8615
 8616    cx.update_buffer(|buffer, cx| {
 8617        buffer.set_language(Some(html_language), cx);
 8618    });
 8619
 8620    cx.set_state(
 8621        &r#"
 8622            <body>ˇ
 8623                <script>
 8624                    var x = 1;ˇ
 8625                </script>
 8626            </body>ˇ
 8627        "#
 8628        .unindent(),
 8629    );
 8630
 8631    // Precondition: different languages are active at different locations.
 8632    cx.update_editor(|editor, window, cx| {
 8633        let snapshot = editor.snapshot(window, cx);
 8634        let cursors = editor.selections.ranges::<usize>(cx);
 8635        let languages = cursors
 8636            .iter()
 8637            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8638            .collect::<Vec<_>>();
 8639        assert_eq!(
 8640            languages,
 8641            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8642        );
 8643    });
 8644
 8645    // Angle brackets autoclose in HTML, but not JavaScript.
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.handle_input("<", window, cx);
 8648        editor.handle_input("a", window, cx);
 8649    });
 8650    cx.assert_editor_state(
 8651        &r#"
 8652            <body><aˇ>
 8653                <script>
 8654                    var x = 1;<aˇ
 8655                </script>
 8656            </body><aˇ>
 8657        "#
 8658        .unindent(),
 8659    );
 8660
 8661    // Curly braces and parens autoclose in both HTML and JavaScript.
 8662    cx.update_editor(|editor, window, cx| {
 8663        editor.handle_input(" b=", window, cx);
 8664        editor.handle_input("{", window, cx);
 8665        editor.handle_input("c", window, cx);
 8666        editor.handle_input("(", window, cx);
 8667    });
 8668    cx.assert_editor_state(
 8669        &r#"
 8670            <body><a b={c(ˇ)}>
 8671                <script>
 8672                    var x = 1;<a b={c(ˇ)}
 8673                </script>
 8674            </body><a b={c(ˇ)}>
 8675        "#
 8676        .unindent(),
 8677    );
 8678
 8679    // Brackets that were already autoclosed are skipped.
 8680    cx.update_editor(|editor, window, cx| {
 8681        editor.handle_input(")", window, cx);
 8682        editor.handle_input("d", window, cx);
 8683        editor.handle_input("}", window, cx);
 8684    });
 8685    cx.assert_editor_state(
 8686        &r#"
 8687            <body><a b={c()d}ˇ>
 8688                <script>
 8689                    var x = 1;<a b={c()d}ˇ
 8690                </script>
 8691            </body><a b={c()d}ˇ>
 8692        "#
 8693        .unindent(),
 8694    );
 8695    cx.update_editor(|editor, window, cx| {
 8696        editor.handle_input(">", window, cx);
 8697    });
 8698    cx.assert_editor_state(
 8699        &r#"
 8700            <body><a b={c()d}>ˇ
 8701                <script>
 8702                    var x = 1;<a b={c()d}>ˇ
 8703                </script>
 8704            </body><a b={c()d}>ˇ
 8705        "#
 8706        .unindent(),
 8707    );
 8708
 8709    // Reset
 8710    cx.set_state(
 8711        &r#"
 8712            <body>ˇ
 8713                <script>
 8714                    var x = 1;ˇ
 8715                </script>
 8716            </body>ˇ
 8717        "#
 8718        .unindent(),
 8719    );
 8720
 8721    cx.update_editor(|editor, window, cx| {
 8722        editor.handle_input("<", window, cx);
 8723    });
 8724    cx.assert_editor_state(
 8725        &r#"
 8726            <body><ˇ>
 8727                <script>
 8728                    var x = 1;<ˇ
 8729                </script>
 8730            </body><ˇ>
 8731        "#
 8732        .unindent(),
 8733    );
 8734
 8735    // When backspacing, the closing angle brackets are removed.
 8736    cx.update_editor(|editor, window, cx| {
 8737        editor.backspace(&Backspace, window, cx);
 8738    });
 8739    cx.assert_editor_state(
 8740        &r#"
 8741            <body>ˇ
 8742                <script>
 8743                    var x = 1;ˇ
 8744                </script>
 8745            </body>ˇ
 8746        "#
 8747        .unindent(),
 8748    );
 8749
 8750    // Block comments autoclose in JavaScript, but not HTML.
 8751    cx.update_editor(|editor, window, cx| {
 8752        editor.handle_input("/", window, cx);
 8753        editor.handle_input("*", window, cx);
 8754    });
 8755    cx.assert_editor_state(
 8756        &r#"
 8757            <body>/*ˇ
 8758                <script>
 8759                    var x = 1;/*ˇ */
 8760                </script>
 8761            </body>/*ˇ
 8762        "#
 8763        .unindent(),
 8764    );
 8765}
 8766
 8767#[gpui::test]
 8768async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8769    init_test(cx, |_| {});
 8770
 8771    let mut cx = EditorTestContext::new(cx).await;
 8772
 8773    let rust_language = Arc::new(
 8774        Language::new(
 8775            LanguageConfig {
 8776                name: "Rust".into(),
 8777                brackets: serde_json::from_value(json!([
 8778                    { "start": "{", "end": "}", "close": true, "newline": true },
 8779                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8780                ]))
 8781                .unwrap(),
 8782                autoclose_before: "})]>".into(),
 8783                ..Default::default()
 8784            },
 8785            Some(tree_sitter_rust::LANGUAGE.into()),
 8786        )
 8787        .with_override_query("(string_literal) @string")
 8788        .unwrap(),
 8789    );
 8790
 8791    cx.language_registry().add(rust_language.clone());
 8792    cx.update_buffer(|buffer, cx| {
 8793        buffer.set_language(Some(rust_language), cx);
 8794    });
 8795
 8796    cx.set_state(
 8797        &r#"
 8798            let x = ˇ
 8799        "#
 8800        .unindent(),
 8801    );
 8802
 8803    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8804    cx.update_editor(|editor, window, cx| {
 8805        editor.handle_input("\"", window, cx);
 8806    });
 8807    cx.assert_editor_state(
 8808        &r#"
 8809            let x = "ˇ"
 8810        "#
 8811        .unindent(),
 8812    );
 8813
 8814    // Inserting another quotation mark. The cursor moves across the existing
 8815    // automatically-inserted quotation mark.
 8816    cx.update_editor(|editor, window, cx| {
 8817        editor.handle_input("\"", window, cx);
 8818    });
 8819    cx.assert_editor_state(
 8820        &r#"
 8821            let x = ""ˇ
 8822        "#
 8823        .unindent(),
 8824    );
 8825
 8826    // Reset
 8827    cx.set_state(
 8828        &r#"
 8829            let x = ˇ
 8830        "#
 8831        .unindent(),
 8832    );
 8833
 8834    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8835    cx.update_editor(|editor, window, cx| {
 8836        editor.handle_input("\"", window, cx);
 8837        editor.handle_input(" ", window, cx);
 8838        editor.move_left(&Default::default(), window, cx);
 8839        editor.handle_input("\\", window, cx);
 8840        editor.handle_input("\"", window, cx);
 8841    });
 8842    cx.assert_editor_state(
 8843        &r#"
 8844            let x = "\"ˇ "
 8845        "#
 8846        .unindent(),
 8847    );
 8848
 8849    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8850    // mark. Nothing is inserted.
 8851    cx.update_editor(|editor, window, cx| {
 8852        editor.move_right(&Default::default(), window, cx);
 8853        editor.handle_input("\"", window, cx);
 8854    });
 8855    cx.assert_editor_state(
 8856        &r#"
 8857            let x = "\" "ˇ
 8858        "#
 8859        .unindent(),
 8860    );
 8861}
 8862
 8863#[gpui::test]
 8864async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8865    init_test(cx, |_| {});
 8866
 8867    let language = Arc::new(Language::new(
 8868        LanguageConfig {
 8869            brackets: BracketPairConfig {
 8870                pairs: vec![
 8871                    BracketPair {
 8872                        start: "{".to_string(),
 8873                        end: "}".to_string(),
 8874                        close: true,
 8875                        surround: true,
 8876                        newline: true,
 8877                    },
 8878                    BracketPair {
 8879                        start: "/* ".to_string(),
 8880                        end: "*/".to_string(),
 8881                        close: true,
 8882                        surround: true,
 8883                        ..Default::default()
 8884                    },
 8885                ],
 8886                ..Default::default()
 8887            },
 8888            ..Default::default()
 8889        },
 8890        Some(tree_sitter_rust::LANGUAGE.into()),
 8891    ));
 8892
 8893    let text = r#"
 8894        a
 8895        b
 8896        c
 8897    "#
 8898    .unindent();
 8899
 8900    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8901    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8902    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8903    editor
 8904        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8905        .await;
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8909            s.select_display_ranges([
 8910                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8911                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8913            ])
 8914        });
 8915
 8916        editor.handle_input("{", window, cx);
 8917        editor.handle_input("{", window, cx);
 8918        editor.handle_input("{", window, cx);
 8919        assert_eq!(
 8920            editor.text(cx),
 8921            "
 8922                {{{a}}}
 8923                {{{b}}}
 8924                {{{c}}}
 8925            "
 8926            .unindent()
 8927        );
 8928        assert_eq!(
 8929            editor.selections.display_ranges(cx),
 8930            [
 8931                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8932                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8933                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8934            ]
 8935        );
 8936
 8937        editor.undo(&Undo, window, cx);
 8938        editor.undo(&Undo, window, cx);
 8939        editor.undo(&Undo, window, cx);
 8940        assert_eq!(
 8941            editor.text(cx),
 8942            "
 8943                a
 8944                b
 8945                c
 8946            "
 8947            .unindent()
 8948        );
 8949        assert_eq!(
 8950            editor.selections.display_ranges(cx),
 8951            [
 8952                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8953                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8954                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8955            ]
 8956        );
 8957
 8958        // Ensure inserting the first character of a multi-byte bracket pair
 8959        // doesn't surround the selections with the bracket.
 8960        editor.handle_input("/", window, cx);
 8961        assert_eq!(
 8962            editor.text(cx),
 8963            "
 8964                /
 8965                /
 8966                /
 8967            "
 8968            .unindent()
 8969        );
 8970        assert_eq!(
 8971            editor.selections.display_ranges(cx),
 8972            [
 8973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8974                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8975                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8976            ]
 8977        );
 8978
 8979        editor.undo(&Undo, window, cx);
 8980        assert_eq!(
 8981            editor.text(cx),
 8982            "
 8983                a
 8984                b
 8985                c
 8986            "
 8987            .unindent()
 8988        );
 8989        assert_eq!(
 8990            editor.selections.display_ranges(cx),
 8991            [
 8992                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8993                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8994                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8995            ]
 8996        );
 8997
 8998        // Ensure inserting the last character of a multi-byte bracket pair
 8999        // doesn't surround the selections with the bracket.
 9000        editor.handle_input("*", window, cx);
 9001        assert_eq!(
 9002            editor.text(cx),
 9003            "
 9004                *
 9005                *
 9006                *
 9007            "
 9008            .unindent()
 9009        );
 9010        assert_eq!(
 9011            editor.selections.display_ranges(cx),
 9012            [
 9013                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9014                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9015                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9016            ]
 9017        );
 9018    });
 9019}
 9020
 9021#[gpui::test]
 9022async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9023    init_test(cx, |_| {});
 9024
 9025    let language = Arc::new(Language::new(
 9026        LanguageConfig {
 9027            brackets: BracketPairConfig {
 9028                pairs: vec![BracketPair {
 9029                    start: "{".to_string(),
 9030                    end: "}".to_string(),
 9031                    close: true,
 9032                    surround: true,
 9033                    newline: true,
 9034                }],
 9035                ..Default::default()
 9036            },
 9037            autoclose_before: "}".to_string(),
 9038            ..Default::default()
 9039        },
 9040        Some(tree_sitter_rust::LANGUAGE.into()),
 9041    ));
 9042
 9043    let text = r#"
 9044        a
 9045        b
 9046        c
 9047    "#
 9048    .unindent();
 9049
 9050    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9051    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9052    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9053    editor
 9054        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9055        .await;
 9056
 9057    editor.update_in(cx, |editor, window, cx| {
 9058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9059            s.select_ranges([
 9060                Point::new(0, 1)..Point::new(0, 1),
 9061                Point::new(1, 1)..Point::new(1, 1),
 9062                Point::new(2, 1)..Point::new(2, 1),
 9063            ])
 9064        });
 9065
 9066        editor.handle_input("{", window, cx);
 9067        editor.handle_input("{", window, cx);
 9068        editor.handle_input("_", window, cx);
 9069        assert_eq!(
 9070            editor.text(cx),
 9071            "
 9072                a{{_}}
 9073                b{{_}}
 9074                c{{_}}
 9075            "
 9076            .unindent()
 9077        );
 9078        assert_eq!(
 9079            editor.selections.ranges::<Point>(cx),
 9080            [
 9081                Point::new(0, 4)..Point::new(0, 4),
 9082                Point::new(1, 4)..Point::new(1, 4),
 9083                Point::new(2, 4)..Point::new(2, 4)
 9084            ]
 9085        );
 9086
 9087        editor.backspace(&Default::default(), window, cx);
 9088        editor.backspace(&Default::default(), window, cx);
 9089        assert_eq!(
 9090            editor.text(cx),
 9091            "
 9092                a{}
 9093                b{}
 9094                c{}
 9095            "
 9096            .unindent()
 9097        );
 9098        assert_eq!(
 9099            editor.selections.ranges::<Point>(cx),
 9100            [
 9101                Point::new(0, 2)..Point::new(0, 2),
 9102                Point::new(1, 2)..Point::new(1, 2),
 9103                Point::new(2, 2)..Point::new(2, 2)
 9104            ]
 9105        );
 9106
 9107        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9108        assert_eq!(
 9109            editor.text(cx),
 9110            "
 9111                a
 9112                b
 9113                c
 9114            "
 9115            .unindent()
 9116        );
 9117        assert_eq!(
 9118            editor.selections.ranges::<Point>(cx),
 9119            [
 9120                Point::new(0, 1)..Point::new(0, 1),
 9121                Point::new(1, 1)..Point::new(1, 1),
 9122                Point::new(2, 1)..Point::new(2, 1)
 9123            ]
 9124        );
 9125    });
 9126}
 9127
 9128#[gpui::test]
 9129async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9130    init_test(cx, |settings| {
 9131        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9132    });
 9133
 9134    let mut cx = EditorTestContext::new(cx).await;
 9135
 9136    let language = Arc::new(Language::new(
 9137        LanguageConfig {
 9138            brackets: BracketPairConfig {
 9139                pairs: vec![
 9140                    BracketPair {
 9141                        start: "{".to_string(),
 9142                        end: "}".to_string(),
 9143                        close: true,
 9144                        surround: true,
 9145                        newline: true,
 9146                    },
 9147                    BracketPair {
 9148                        start: "(".to_string(),
 9149                        end: ")".to_string(),
 9150                        close: true,
 9151                        surround: true,
 9152                        newline: true,
 9153                    },
 9154                    BracketPair {
 9155                        start: "[".to_string(),
 9156                        end: "]".to_string(),
 9157                        close: false,
 9158                        surround: true,
 9159                        newline: true,
 9160                    },
 9161                ],
 9162                ..Default::default()
 9163            },
 9164            autoclose_before: "})]".to_string(),
 9165            ..Default::default()
 9166        },
 9167        Some(tree_sitter_rust::LANGUAGE.into()),
 9168    ));
 9169
 9170    cx.language_registry().add(language.clone());
 9171    cx.update_buffer(|buffer, cx| {
 9172        buffer.set_language(Some(language), cx);
 9173    });
 9174
 9175    cx.set_state(
 9176        &"
 9177            {(ˇ)}
 9178            [[ˇ]]
 9179            {(ˇ)}
 9180        "
 9181        .unindent(),
 9182    );
 9183
 9184    cx.update_editor(|editor, window, cx| {
 9185        editor.backspace(&Default::default(), window, cx);
 9186        editor.backspace(&Default::default(), window, cx);
 9187    });
 9188
 9189    cx.assert_editor_state(
 9190        &"
 9191            ˇ
 9192            ˇ]]
 9193            ˇ
 9194        "
 9195        .unindent(),
 9196    );
 9197
 9198    cx.update_editor(|editor, window, cx| {
 9199        editor.handle_input("{", window, cx);
 9200        editor.handle_input("{", window, cx);
 9201        editor.move_right(&MoveRight, window, cx);
 9202        editor.move_right(&MoveRight, window, cx);
 9203        editor.move_left(&MoveLeft, window, cx);
 9204        editor.move_left(&MoveLeft, window, cx);
 9205        editor.backspace(&Default::default(), window, cx);
 9206    });
 9207
 9208    cx.assert_editor_state(
 9209        &"
 9210            {ˇ}
 9211            {ˇ}]]
 9212            {ˇ}
 9213        "
 9214        .unindent(),
 9215    );
 9216
 9217    cx.update_editor(|editor, window, cx| {
 9218        editor.backspace(&Default::default(), window, cx);
 9219    });
 9220
 9221    cx.assert_editor_state(
 9222        &"
 9223            ˇ
 9224            ˇ]]
 9225            ˇ
 9226        "
 9227        .unindent(),
 9228    );
 9229}
 9230
 9231#[gpui::test]
 9232async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9233    init_test(cx, |_| {});
 9234
 9235    let language = Arc::new(Language::new(
 9236        LanguageConfig::default(),
 9237        Some(tree_sitter_rust::LANGUAGE.into()),
 9238    ));
 9239
 9240    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9241    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9242    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9243    editor
 9244        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9245        .await;
 9246
 9247    editor.update_in(cx, |editor, window, cx| {
 9248        editor.set_auto_replace_emoji_shortcode(true);
 9249
 9250        editor.handle_input("Hello ", window, cx);
 9251        editor.handle_input(":wave", window, cx);
 9252        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9253
 9254        editor.handle_input(":", window, cx);
 9255        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9256
 9257        editor.handle_input(" :smile", window, cx);
 9258        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9259
 9260        editor.handle_input(":", window, cx);
 9261        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9262
 9263        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9264        editor.handle_input(":wave", window, cx);
 9265        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9266
 9267        editor.handle_input(":", window, cx);
 9268        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9269
 9270        editor.handle_input(":1", window, cx);
 9271        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9272
 9273        editor.handle_input(":", window, cx);
 9274        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9275
 9276        // Ensure shortcode does not get replaced when it is part of a word
 9277        editor.handle_input(" Test:wave", window, cx);
 9278        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9279
 9280        editor.handle_input(":", window, cx);
 9281        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9282
 9283        editor.set_auto_replace_emoji_shortcode(false);
 9284
 9285        // Ensure shortcode does not get replaced when auto replace is off
 9286        editor.handle_input(" :wave", window, cx);
 9287        assert_eq!(
 9288            editor.text(cx),
 9289            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9290        );
 9291
 9292        editor.handle_input(":", window, cx);
 9293        assert_eq!(
 9294            editor.text(cx),
 9295            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9296        );
 9297    });
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    let (text, insertion_ranges) = marked_text_ranges(
 9305        indoc! {"
 9306            ˇ
 9307        "},
 9308        false,
 9309    );
 9310
 9311    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9312    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9313
 9314    _ = editor.update_in(cx, |editor, window, cx| {
 9315        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9316
 9317        editor
 9318            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9319            .unwrap();
 9320
 9321        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9322            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9323            assert_eq!(editor.text(cx), expected_text);
 9324            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9325        }
 9326
 9327        assert(
 9328            editor,
 9329            cx,
 9330            indoc! {"
 9331            type «» =•
 9332            "},
 9333        );
 9334
 9335        assert!(editor.context_menu_visible(), "There should be a matches");
 9336    });
 9337}
 9338
 9339#[gpui::test]
 9340async fn test_snippets(cx: &mut TestAppContext) {
 9341    init_test(cx, |_| {});
 9342
 9343    let mut cx = EditorTestContext::new(cx).await;
 9344
 9345    cx.set_state(indoc! {"
 9346        a.ˇ b
 9347        a.ˇ b
 9348        a.ˇ b
 9349    "});
 9350
 9351    cx.update_editor(|editor, window, cx| {
 9352        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9353        let insertion_ranges = editor
 9354            .selections
 9355            .all(cx)
 9356            .iter()
 9357            .map(|s| s.range().clone())
 9358            .collect::<Vec<_>>();
 9359        editor
 9360            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9361            .unwrap();
 9362    });
 9363
 9364    cx.assert_editor_state(indoc! {"
 9365        a.f(«oneˇ», two, «threeˇ») b
 9366        a.f(«oneˇ», two, «threeˇ») b
 9367        a.f(«oneˇ», two, «threeˇ») b
 9368    "});
 9369
 9370    // Can't move earlier than the first tab stop
 9371    cx.update_editor(|editor, window, cx| {
 9372        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9373    });
 9374    cx.assert_editor_state(indoc! {"
 9375        a.f(«oneˇ», two, «threeˇ») b
 9376        a.f(«oneˇ», two, «threeˇ») b
 9377        a.f(«oneˇ», two, «threeˇ») b
 9378    "});
 9379
 9380    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9381    cx.assert_editor_state(indoc! {"
 9382        a.f(one, «twoˇ», three) b
 9383        a.f(one, «twoˇ», three) b
 9384        a.f(one, «twoˇ», three) b
 9385    "});
 9386
 9387    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9388    cx.assert_editor_state(indoc! {"
 9389        a.f(«oneˇ», two, «threeˇ») b
 9390        a.f(«oneˇ», two, «threeˇ») b
 9391        a.f(«oneˇ», two, «threeˇ») b
 9392    "});
 9393
 9394    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9395    cx.assert_editor_state(indoc! {"
 9396        a.f(one, «twoˇ», three) b
 9397        a.f(one, «twoˇ», three) b
 9398        a.f(one, «twoˇ», three) b
 9399    "});
 9400    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9401    cx.assert_editor_state(indoc! {"
 9402        a.f(one, two, three)ˇ b
 9403        a.f(one, two, three)ˇ b
 9404        a.f(one, two, three)ˇ b
 9405    "});
 9406
 9407    // As soon as the last tab stop is reached, snippet state is gone
 9408    cx.update_editor(|editor, window, cx| {
 9409        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9410    });
 9411    cx.assert_editor_state(indoc! {"
 9412        a.f(one, two, three)ˇ b
 9413        a.f(one, two, three)ˇ b
 9414        a.f(one, two, three)ˇ b
 9415    "});
 9416}
 9417
 9418#[gpui::test]
 9419async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9420    init_test(cx, |_| {});
 9421
 9422    let mut cx = EditorTestContext::new(cx).await;
 9423
 9424    cx.update_editor(|editor, window, cx| {
 9425        let snippet = Snippet::parse(indoc! {"
 9426            /*
 9427             * Multiline comment with leading indentation
 9428             *
 9429             * $1
 9430             */
 9431            $0"})
 9432        .unwrap();
 9433        let insertion_ranges = editor
 9434            .selections
 9435            .all(cx)
 9436            .iter()
 9437            .map(|s| s.range().clone())
 9438            .collect::<Vec<_>>();
 9439        editor
 9440            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9441            .unwrap();
 9442    });
 9443
 9444    cx.assert_editor_state(indoc! {"
 9445        /*
 9446         * Multiline comment with leading indentation
 9447         *
 9448         * ˇ
 9449         */
 9450    "});
 9451
 9452    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9453    cx.assert_editor_state(indoc! {"
 9454        /*
 9455         * Multiline comment with leading indentation
 9456         *
 9457         *•
 9458         */
 9459        ˇ"});
 9460}
 9461
 9462#[gpui::test]
 9463async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9464    init_test(cx, |_| {});
 9465
 9466    let fs = FakeFs::new(cx.executor());
 9467    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9468
 9469    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9470
 9471    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9472    language_registry.add(rust_lang());
 9473    let mut fake_servers = language_registry.register_fake_lsp(
 9474        "Rust",
 9475        FakeLspAdapter {
 9476            capabilities: lsp::ServerCapabilities {
 9477                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9478                ..Default::default()
 9479            },
 9480            ..Default::default()
 9481        },
 9482    );
 9483
 9484    let buffer = project
 9485        .update(cx, |project, cx| {
 9486            project.open_local_buffer(path!("/file.rs"), cx)
 9487        })
 9488        .await
 9489        .unwrap();
 9490
 9491    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9492    let (editor, cx) = cx.add_window_view(|window, cx| {
 9493        build_editor_with_project(project.clone(), buffer, window, cx)
 9494    });
 9495    editor.update_in(cx, |editor, window, cx| {
 9496        editor.set_text("one\ntwo\nthree\n", window, cx)
 9497    });
 9498    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9499
 9500    cx.executor().start_waiting();
 9501    let fake_server = fake_servers.next().await.unwrap();
 9502
 9503    {
 9504        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9505            move |params, _| async move {
 9506                assert_eq!(
 9507                    params.text_document.uri,
 9508                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9509                );
 9510                assert_eq!(params.options.tab_size, 4);
 9511                Ok(Some(vec![lsp::TextEdit::new(
 9512                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9513                    ", ".to_string(),
 9514                )]))
 9515            },
 9516        );
 9517        let save = editor
 9518            .update_in(cx, |editor, window, cx| {
 9519                editor.save(
 9520                    SaveOptions {
 9521                        format: true,
 9522                        autosave: false,
 9523                    },
 9524                    project.clone(),
 9525                    window,
 9526                    cx,
 9527                )
 9528            })
 9529            .unwrap();
 9530        cx.executor().start_waiting();
 9531        save.await;
 9532
 9533        assert_eq!(
 9534            editor.update(cx, |editor, cx| editor.text(cx)),
 9535            "one, two\nthree\n"
 9536        );
 9537        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9538    }
 9539
 9540    {
 9541        editor.update_in(cx, |editor, window, cx| {
 9542            editor.set_text("one\ntwo\nthree\n", window, cx)
 9543        });
 9544        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9545
 9546        // Ensure we can still save even if formatting hangs.
 9547        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9548            move |params, _| async move {
 9549                assert_eq!(
 9550                    params.text_document.uri,
 9551                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9552                );
 9553                futures::future::pending::<()>().await;
 9554                unreachable!()
 9555            },
 9556        );
 9557        let save = editor
 9558            .update_in(cx, |editor, window, cx| {
 9559                editor.save(
 9560                    SaveOptions {
 9561                        format: true,
 9562                        autosave: false,
 9563                    },
 9564                    project.clone(),
 9565                    window,
 9566                    cx,
 9567                )
 9568            })
 9569            .unwrap();
 9570        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9571        cx.executor().start_waiting();
 9572        save.await;
 9573        assert_eq!(
 9574            editor.update(cx, |editor, cx| editor.text(cx)),
 9575            "one\ntwo\nthree\n"
 9576        );
 9577    }
 9578
 9579    // Set rust language override and assert overridden tabsize is sent to language server
 9580    update_test_language_settings(cx, |settings| {
 9581        settings.languages.0.insert(
 9582            "Rust".into(),
 9583            LanguageSettingsContent {
 9584                tab_size: NonZeroU32::new(8),
 9585                ..Default::default()
 9586            },
 9587        );
 9588    });
 9589
 9590    {
 9591        editor.update_in(cx, |editor, window, cx| {
 9592            editor.set_text("somehting_new\n", window, cx)
 9593        });
 9594        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9595        let _formatting_request_signal = fake_server
 9596            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9597                assert_eq!(
 9598                    params.text_document.uri,
 9599                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9600                );
 9601                assert_eq!(params.options.tab_size, 8);
 9602                Ok(Some(vec![]))
 9603            });
 9604        let save = editor
 9605            .update_in(cx, |editor, window, cx| {
 9606                editor.save(
 9607                    SaveOptions {
 9608                        format: true,
 9609                        autosave: false,
 9610                    },
 9611                    project.clone(),
 9612                    window,
 9613                    cx,
 9614                )
 9615            })
 9616            .unwrap();
 9617        cx.executor().start_waiting();
 9618        save.await;
 9619    }
 9620}
 9621
 9622#[gpui::test]
 9623async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9624    init_test(cx, |settings| {
 9625        settings.defaults.ensure_final_newline_on_save = Some(false);
 9626    });
 9627
 9628    let fs = FakeFs::new(cx.executor());
 9629    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9630
 9631    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9632
 9633    let buffer = project
 9634        .update(cx, |project, cx| {
 9635            project.open_local_buffer(path!("/file.txt"), cx)
 9636        })
 9637        .await
 9638        .unwrap();
 9639
 9640    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9641    let (editor, cx) = cx.add_window_view(|window, cx| {
 9642        build_editor_with_project(project.clone(), buffer, window, cx)
 9643    });
 9644    editor.update_in(cx, |editor, window, cx| {
 9645        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9646            s.select_ranges([0..0])
 9647        });
 9648    });
 9649    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9650
 9651    editor.update_in(cx, |editor, window, cx| {
 9652        editor.handle_input("\n", window, cx)
 9653    });
 9654    cx.run_until_parked();
 9655    save(&editor, &project, cx).await;
 9656    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9657
 9658    editor.update_in(cx, |editor, window, cx| {
 9659        editor.undo(&Default::default(), window, cx);
 9660    });
 9661    save(&editor, &project, cx).await;
 9662    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9663
 9664    editor.update_in(cx, |editor, window, cx| {
 9665        editor.redo(&Default::default(), window, cx);
 9666    });
 9667    cx.run_until_parked();
 9668    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9669
 9670    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9671        let save = editor
 9672            .update_in(cx, |editor, window, cx| {
 9673                editor.save(
 9674                    SaveOptions {
 9675                        format: true,
 9676                        autosave: false,
 9677                    },
 9678                    project.clone(),
 9679                    window,
 9680                    cx,
 9681                )
 9682            })
 9683            .unwrap();
 9684        cx.executor().start_waiting();
 9685        save.await;
 9686        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9687    }
 9688}
 9689
 9690#[gpui::test]
 9691async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9692    init_test(cx, |_| {});
 9693
 9694    let cols = 4;
 9695    let rows = 10;
 9696    let sample_text_1 = sample_text(rows, cols, 'a');
 9697    assert_eq!(
 9698        sample_text_1,
 9699        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9700    );
 9701    let sample_text_2 = sample_text(rows, cols, 'l');
 9702    assert_eq!(
 9703        sample_text_2,
 9704        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9705    );
 9706    let sample_text_3 = sample_text(rows, cols, 'v');
 9707    assert_eq!(
 9708        sample_text_3,
 9709        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9710    );
 9711
 9712    let fs = FakeFs::new(cx.executor());
 9713    fs.insert_tree(
 9714        path!("/a"),
 9715        json!({
 9716            "main.rs": sample_text_1,
 9717            "other.rs": sample_text_2,
 9718            "lib.rs": sample_text_3,
 9719        }),
 9720    )
 9721    .await;
 9722
 9723    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9725    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9726
 9727    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9728    language_registry.add(rust_lang());
 9729    let mut fake_servers = language_registry.register_fake_lsp(
 9730        "Rust",
 9731        FakeLspAdapter {
 9732            capabilities: lsp::ServerCapabilities {
 9733                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9734                ..Default::default()
 9735            },
 9736            ..Default::default()
 9737        },
 9738    );
 9739
 9740    let worktree = project.update(cx, |project, cx| {
 9741        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9742        assert_eq!(worktrees.len(), 1);
 9743        worktrees.pop().unwrap()
 9744    });
 9745    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9746
 9747    let buffer_1 = project
 9748        .update(cx, |project, cx| {
 9749            project.open_buffer((worktree_id, "main.rs"), cx)
 9750        })
 9751        .await
 9752        .unwrap();
 9753    let buffer_2 = project
 9754        .update(cx, |project, cx| {
 9755            project.open_buffer((worktree_id, "other.rs"), cx)
 9756        })
 9757        .await
 9758        .unwrap();
 9759    let buffer_3 = project
 9760        .update(cx, |project, cx| {
 9761            project.open_buffer((worktree_id, "lib.rs"), cx)
 9762        })
 9763        .await
 9764        .unwrap();
 9765
 9766    let multi_buffer = cx.new(|cx| {
 9767        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9768        multi_buffer.push_excerpts(
 9769            buffer_1.clone(),
 9770            [
 9771                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9772                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9773                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9774            ],
 9775            cx,
 9776        );
 9777        multi_buffer.push_excerpts(
 9778            buffer_2.clone(),
 9779            [
 9780                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9781                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9782                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9783            ],
 9784            cx,
 9785        );
 9786        multi_buffer.push_excerpts(
 9787            buffer_3.clone(),
 9788            [
 9789                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9790                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9791                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9792            ],
 9793            cx,
 9794        );
 9795        multi_buffer
 9796    });
 9797    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9798        Editor::new(
 9799            EditorMode::full(),
 9800            multi_buffer,
 9801            Some(project.clone()),
 9802            window,
 9803            cx,
 9804        )
 9805    });
 9806
 9807    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9808        editor.change_selections(
 9809            SelectionEffects::scroll(Autoscroll::Next),
 9810            window,
 9811            cx,
 9812            |s| s.select_ranges(Some(1..2)),
 9813        );
 9814        editor.insert("|one|two|three|", window, cx);
 9815    });
 9816    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9817    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9818        editor.change_selections(
 9819            SelectionEffects::scroll(Autoscroll::Next),
 9820            window,
 9821            cx,
 9822            |s| s.select_ranges(Some(60..70)),
 9823        );
 9824        editor.insert("|four|five|six|", window, cx);
 9825    });
 9826    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9827
 9828    // First two buffers should be edited, but not the third one.
 9829    assert_eq!(
 9830        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9831        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 9832    );
 9833    buffer_1.update(cx, |buffer, _| {
 9834        assert!(buffer.is_dirty());
 9835        assert_eq!(
 9836            buffer.text(),
 9837            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9838        )
 9839    });
 9840    buffer_2.update(cx, |buffer, _| {
 9841        assert!(buffer.is_dirty());
 9842        assert_eq!(
 9843            buffer.text(),
 9844            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9845        )
 9846    });
 9847    buffer_3.update(cx, |buffer, _| {
 9848        assert!(!buffer.is_dirty());
 9849        assert_eq!(buffer.text(), sample_text_3,)
 9850    });
 9851    cx.executor().run_until_parked();
 9852
 9853    cx.executor().start_waiting();
 9854    let save = multi_buffer_editor
 9855        .update_in(cx, |editor, window, cx| {
 9856            editor.save(
 9857                SaveOptions {
 9858                    format: true,
 9859                    autosave: false,
 9860                },
 9861                project.clone(),
 9862                window,
 9863                cx,
 9864            )
 9865        })
 9866        .unwrap();
 9867
 9868    let fake_server = fake_servers.next().await.unwrap();
 9869    fake_server
 9870        .server
 9871        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9872            Ok(Some(vec![lsp::TextEdit::new(
 9873                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9874                format!("[{} formatted]", params.text_document.uri),
 9875            )]))
 9876        })
 9877        .detach();
 9878    save.await;
 9879
 9880    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9881    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9882    assert_eq!(
 9883        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9884        uri!(
 9885            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9886        ),
 9887    );
 9888    buffer_1.update(cx, |buffer, _| {
 9889        assert!(!buffer.is_dirty());
 9890        assert_eq!(
 9891            buffer.text(),
 9892            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9893        )
 9894    });
 9895    buffer_2.update(cx, |buffer, _| {
 9896        assert!(!buffer.is_dirty());
 9897        assert_eq!(
 9898            buffer.text(),
 9899            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9900        )
 9901    });
 9902    buffer_3.update(cx, |buffer, _| {
 9903        assert!(!buffer.is_dirty());
 9904        assert_eq!(buffer.text(), sample_text_3,)
 9905    });
 9906}
 9907
 9908#[gpui::test]
 9909async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9910    init_test(cx, |_| {});
 9911
 9912    let fs = FakeFs::new(cx.executor());
 9913    fs.insert_tree(
 9914        path!("/dir"),
 9915        json!({
 9916            "file1.rs": "fn main() { println!(\"hello\"); }",
 9917            "file2.rs": "fn test() { println!(\"test\"); }",
 9918            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9919        }),
 9920    )
 9921    .await;
 9922
 9923    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9924    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9925    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9926
 9927    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9928    language_registry.add(rust_lang());
 9929
 9930    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9931    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9932
 9933    // Open three buffers
 9934    let buffer_1 = project
 9935        .update(cx, |project, cx| {
 9936            project.open_buffer((worktree_id, "file1.rs"), cx)
 9937        })
 9938        .await
 9939        .unwrap();
 9940    let buffer_2 = project
 9941        .update(cx, |project, cx| {
 9942            project.open_buffer((worktree_id, "file2.rs"), cx)
 9943        })
 9944        .await
 9945        .unwrap();
 9946    let buffer_3 = project
 9947        .update(cx, |project, cx| {
 9948            project.open_buffer((worktree_id, "file3.rs"), cx)
 9949        })
 9950        .await
 9951        .unwrap();
 9952
 9953    // Create a multi-buffer with all three buffers
 9954    let multi_buffer = cx.new(|cx| {
 9955        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9956        multi_buffer.push_excerpts(
 9957            buffer_1.clone(),
 9958            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9959            cx,
 9960        );
 9961        multi_buffer.push_excerpts(
 9962            buffer_2.clone(),
 9963            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9964            cx,
 9965        );
 9966        multi_buffer.push_excerpts(
 9967            buffer_3.clone(),
 9968            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9969            cx,
 9970        );
 9971        multi_buffer
 9972    });
 9973
 9974    let editor = cx.new_window_entity(|window, cx| {
 9975        Editor::new(
 9976            EditorMode::full(),
 9977            multi_buffer,
 9978            Some(project.clone()),
 9979            window,
 9980            cx,
 9981        )
 9982    });
 9983
 9984    // Edit only the first buffer
 9985    editor.update_in(cx, |editor, window, cx| {
 9986        editor.change_selections(
 9987            SelectionEffects::scroll(Autoscroll::Next),
 9988            window,
 9989            cx,
 9990            |s| s.select_ranges(Some(10..10)),
 9991        );
 9992        editor.insert("// edited", window, cx);
 9993    });
 9994
 9995    // Verify that only buffer 1 is dirty
 9996    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9997    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9998    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9999
10000    // Get write counts after file creation (files were created with initial content)
10001    // We expect each file to have been written once during creation
10002    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10003    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10004    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10005
10006    // Perform autosave
10007    let save_task = editor.update_in(cx, |editor, window, cx| {
10008        editor.save(
10009            SaveOptions {
10010                format: true,
10011                autosave: true,
10012            },
10013            project.clone(),
10014            window,
10015            cx,
10016        )
10017    });
10018    save_task.await.unwrap();
10019
10020    // Only the dirty buffer should have been saved
10021    assert_eq!(
10022        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10023        1,
10024        "Buffer 1 was dirty, so it should have been written once during autosave"
10025    );
10026    assert_eq!(
10027        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10028        0,
10029        "Buffer 2 was clean, so it should not have been written during autosave"
10030    );
10031    assert_eq!(
10032        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10033        0,
10034        "Buffer 3 was clean, so it should not have been written during autosave"
10035    );
10036
10037    // Verify buffer states after autosave
10038    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10039    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10040    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10041
10042    // Now perform a manual save (format = true)
10043    let save_task = editor.update_in(cx, |editor, window, cx| {
10044        editor.save(
10045            SaveOptions {
10046                format: true,
10047                autosave: false,
10048            },
10049            project.clone(),
10050            window,
10051            cx,
10052        )
10053    });
10054    save_task.await.unwrap();
10055
10056    // During manual save, clean buffers don't get written to disk
10057    // They just get did_save called for language server notifications
10058    assert_eq!(
10059        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10060        1,
10061        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10062    );
10063    assert_eq!(
10064        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10065        0,
10066        "Buffer 2 should not have been written at all"
10067    );
10068    assert_eq!(
10069        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10070        0,
10071        "Buffer 3 should not have been written at all"
10072    );
10073}
10074
10075async fn setup_range_format_test(
10076    cx: &mut TestAppContext,
10077) -> (
10078    Entity<Project>,
10079    Entity<Editor>,
10080    &mut gpui::VisualTestContext,
10081    lsp::FakeLanguageServer,
10082) {
10083    init_test(cx, |_| {});
10084
10085    let fs = FakeFs::new(cx.executor());
10086    fs.insert_file(path!("/file.rs"), Default::default()).await;
10087
10088    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10089
10090    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10091    language_registry.add(rust_lang());
10092    let mut fake_servers = language_registry.register_fake_lsp(
10093        "Rust",
10094        FakeLspAdapter {
10095            capabilities: lsp::ServerCapabilities {
10096                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10097                ..lsp::ServerCapabilities::default()
10098            },
10099            ..FakeLspAdapter::default()
10100        },
10101    );
10102
10103    let buffer = project
10104        .update(cx, |project, cx| {
10105            project.open_local_buffer(path!("/file.rs"), cx)
10106        })
10107        .await
10108        .unwrap();
10109
10110    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10111    let (editor, cx) = cx.add_window_view(|window, cx| {
10112        build_editor_with_project(project.clone(), buffer, window, cx)
10113    });
10114
10115    cx.executor().start_waiting();
10116    let fake_server = fake_servers.next().await.unwrap();
10117
10118    (project, editor, cx, fake_server)
10119}
10120
10121#[gpui::test]
10122async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10123    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10124
10125    editor.update_in(cx, |editor, window, cx| {
10126        editor.set_text("one\ntwo\nthree\n", window, cx)
10127    });
10128    assert!(cx.read(|cx| editor.is_dirty(cx)));
10129
10130    let save = editor
10131        .update_in(cx, |editor, window, cx| {
10132            editor.save(
10133                SaveOptions {
10134                    format: true,
10135                    autosave: false,
10136                },
10137                project.clone(),
10138                window,
10139                cx,
10140            )
10141        })
10142        .unwrap();
10143    fake_server
10144        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10145            assert_eq!(
10146                params.text_document.uri,
10147                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10148            );
10149            assert_eq!(params.options.tab_size, 4);
10150            Ok(Some(vec![lsp::TextEdit::new(
10151                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10152                ", ".to_string(),
10153            )]))
10154        })
10155        .next()
10156        .await;
10157    cx.executor().start_waiting();
10158    save.await;
10159    assert_eq!(
10160        editor.update(cx, |editor, cx| editor.text(cx)),
10161        "one, two\nthree\n"
10162    );
10163    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10164}
10165
10166#[gpui::test]
10167async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10168    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10169
10170    editor.update_in(cx, |editor, window, cx| {
10171        editor.set_text("one\ntwo\nthree\n", window, cx)
10172    });
10173    assert!(cx.read(|cx| editor.is_dirty(cx)));
10174
10175    // Test that save still works when formatting hangs
10176    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10177        move |params, _| async move {
10178            assert_eq!(
10179                params.text_document.uri,
10180                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10181            );
10182            futures::future::pending::<()>().await;
10183            unreachable!()
10184        },
10185    );
10186    let save = editor
10187        .update_in(cx, |editor, window, cx| {
10188            editor.save(
10189                SaveOptions {
10190                    format: true,
10191                    autosave: false,
10192                },
10193                project.clone(),
10194                window,
10195                cx,
10196            )
10197        })
10198        .unwrap();
10199    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10200    cx.executor().start_waiting();
10201    save.await;
10202    assert_eq!(
10203        editor.update(cx, |editor, cx| editor.text(cx)),
10204        "one\ntwo\nthree\n"
10205    );
10206    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10207}
10208
10209#[gpui::test]
10210async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10211    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10212
10213    // Buffer starts clean, no formatting should be requested
10214    let save = editor
10215        .update_in(cx, |editor, window, cx| {
10216            editor.save(
10217                SaveOptions {
10218                    format: false,
10219                    autosave: false,
10220                },
10221                project.clone(),
10222                window,
10223                cx,
10224            )
10225        })
10226        .unwrap();
10227    let _pending_format_request = fake_server
10228        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10229            panic!("Should not be invoked");
10230        })
10231        .next();
10232    cx.executor().start_waiting();
10233    save.await;
10234    cx.run_until_parked();
10235}
10236
10237#[gpui::test]
10238async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10239    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10240
10241    // Set Rust language override and assert overridden tabsize is sent to language server
10242    update_test_language_settings(cx, |settings| {
10243        settings.languages.0.insert(
10244            "Rust".into(),
10245            LanguageSettingsContent {
10246                tab_size: NonZeroU32::new(8),
10247                ..Default::default()
10248            },
10249        );
10250    });
10251
10252    editor.update_in(cx, |editor, window, cx| {
10253        editor.set_text("something_new\n", window, cx)
10254    });
10255    assert!(cx.read(|cx| editor.is_dirty(cx)));
10256    let save = editor
10257        .update_in(cx, |editor, window, cx| {
10258            editor.save(
10259                SaveOptions {
10260                    format: true,
10261                    autosave: false,
10262                },
10263                project.clone(),
10264                window,
10265                cx,
10266            )
10267        })
10268        .unwrap();
10269    fake_server
10270        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10271            assert_eq!(
10272                params.text_document.uri,
10273                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10274            );
10275            assert_eq!(params.options.tab_size, 8);
10276            Ok(Some(Vec::new()))
10277        })
10278        .next()
10279        .await;
10280    save.await;
10281}
10282
10283#[gpui::test]
10284async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10285    init_test(cx, |settings| {
10286        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10287            Formatter::LanguageServer { name: None },
10288        )))
10289    });
10290
10291    let fs = FakeFs::new(cx.executor());
10292    fs.insert_file(path!("/file.rs"), Default::default()).await;
10293
10294    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10295
10296    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10297    language_registry.add(Arc::new(Language::new(
10298        LanguageConfig {
10299            name: "Rust".into(),
10300            matcher: LanguageMatcher {
10301                path_suffixes: vec!["rs".to_string()],
10302                ..Default::default()
10303            },
10304            ..LanguageConfig::default()
10305        },
10306        Some(tree_sitter_rust::LANGUAGE.into()),
10307    )));
10308    update_test_language_settings(cx, |settings| {
10309        // Enable Prettier formatting for the same buffer, and ensure
10310        // LSP is called instead of Prettier.
10311        settings.defaults.prettier = Some(PrettierSettings {
10312            allowed: true,
10313            ..PrettierSettings::default()
10314        });
10315    });
10316    let mut fake_servers = language_registry.register_fake_lsp(
10317        "Rust",
10318        FakeLspAdapter {
10319            capabilities: lsp::ServerCapabilities {
10320                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10321                ..Default::default()
10322            },
10323            ..Default::default()
10324        },
10325    );
10326
10327    let buffer = project
10328        .update(cx, |project, cx| {
10329            project.open_local_buffer(path!("/file.rs"), cx)
10330        })
10331        .await
10332        .unwrap();
10333
10334    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10335    let (editor, cx) = cx.add_window_view(|window, cx| {
10336        build_editor_with_project(project.clone(), buffer, window, cx)
10337    });
10338    editor.update_in(cx, |editor, window, cx| {
10339        editor.set_text("one\ntwo\nthree\n", window, cx)
10340    });
10341
10342    cx.executor().start_waiting();
10343    let fake_server = fake_servers.next().await.unwrap();
10344
10345    let format = editor
10346        .update_in(cx, |editor, window, cx| {
10347            editor.perform_format(
10348                project.clone(),
10349                FormatTrigger::Manual,
10350                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10351                window,
10352                cx,
10353            )
10354        })
10355        .unwrap();
10356    fake_server
10357        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10358            assert_eq!(
10359                params.text_document.uri,
10360                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10361            );
10362            assert_eq!(params.options.tab_size, 4);
10363            Ok(Some(vec![lsp::TextEdit::new(
10364                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10365                ", ".to_string(),
10366            )]))
10367        })
10368        .next()
10369        .await;
10370    cx.executor().start_waiting();
10371    format.await;
10372    assert_eq!(
10373        editor.update(cx, |editor, cx| editor.text(cx)),
10374        "one, two\nthree\n"
10375    );
10376
10377    editor.update_in(cx, |editor, window, cx| {
10378        editor.set_text("one\ntwo\nthree\n", window, cx)
10379    });
10380    // Ensure we don't lock if formatting hangs.
10381    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10382        move |params, _| async move {
10383            assert_eq!(
10384                params.text_document.uri,
10385                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10386            );
10387            futures::future::pending::<()>().await;
10388            unreachable!()
10389        },
10390    );
10391    let format = editor
10392        .update_in(cx, |editor, window, cx| {
10393            editor.perform_format(
10394                project,
10395                FormatTrigger::Manual,
10396                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10397                window,
10398                cx,
10399            )
10400        })
10401        .unwrap();
10402    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10403    cx.executor().start_waiting();
10404    format.await;
10405    assert_eq!(
10406        editor.update(cx, |editor, cx| editor.text(cx)),
10407        "one\ntwo\nthree\n"
10408    );
10409}
10410
10411#[gpui::test]
10412async fn test_multiple_formatters(cx: &mut TestAppContext) {
10413    init_test(cx, |settings| {
10414        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10415        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10416            Formatter::LanguageServer { name: None },
10417            Formatter::CodeActions(
10418                [
10419                    ("code-action-1".into(), true),
10420                    ("code-action-2".into(), true),
10421                ]
10422                .into_iter()
10423                .collect(),
10424            ),
10425        ])))
10426    });
10427
10428    let fs = FakeFs::new(cx.executor());
10429    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10430        .await;
10431
10432    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10433    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10434    language_registry.add(rust_lang());
10435
10436    let mut fake_servers = language_registry.register_fake_lsp(
10437        "Rust",
10438        FakeLspAdapter {
10439            capabilities: lsp::ServerCapabilities {
10440                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10441                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10442                    commands: vec!["the-command-for-code-action-1".into()],
10443                    ..Default::default()
10444                }),
10445                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10446                ..Default::default()
10447            },
10448            ..Default::default()
10449        },
10450    );
10451
10452    let buffer = project
10453        .update(cx, |project, cx| {
10454            project.open_local_buffer(path!("/file.rs"), cx)
10455        })
10456        .await
10457        .unwrap();
10458
10459    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10460    let (editor, cx) = cx.add_window_view(|window, cx| {
10461        build_editor_with_project(project.clone(), buffer, window, cx)
10462    });
10463
10464    cx.executor().start_waiting();
10465
10466    let fake_server = fake_servers.next().await.unwrap();
10467    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10468        move |_params, _| async move {
10469            Ok(Some(vec![lsp::TextEdit::new(
10470                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10471                "applied-formatting\n".to_string(),
10472            )]))
10473        },
10474    );
10475    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10476        move |params, _| async move {
10477            assert_eq!(
10478                params.context.only,
10479                Some(vec!["code-action-1".into(), "code-action-2".into()])
10480            );
10481            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10482            Ok(Some(vec![
10483                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10484                    kind: Some("code-action-1".into()),
10485                    edit: Some(lsp::WorkspaceEdit::new(
10486                        [(
10487                            uri.clone(),
10488                            vec![lsp::TextEdit::new(
10489                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10490                                "applied-code-action-1-edit\n".to_string(),
10491                            )],
10492                        )]
10493                        .into_iter()
10494                        .collect(),
10495                    )),
10496                    command: Some(lsp::Command {
10497                        command: "the-command-for-code-action-1".into(),
10498                        ..Default::default()
10499                    }),
10500                    ..Default::default()
10501                }),
10502                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10503                    kind: Some("code-action-2".into()),
10504                    edit: Some(lsp::WorkspaceEdit::new(
10505                        [(
10506                            uri.clone(),
10507                            vec![lsp::TextEdit::new(
10508                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10509                                "applied-code-action-2-edit\n".to_string(),
10510                            )],
10511                        )]
10512                        .into_iter()
10513                        .collect(),
10514                    )),
10515                    ..Default::default()
10516                }),
10517            ]))
10518        },
10519    );
10520
10521    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10522        move |params, _| async move { Ok(params) }
10523    });
10524
10525    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10526    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10527        let fake = fake_server.clone();
10528        let lock = command_lock.clone();
10529        move |params, _| {
10530            assert_eq!(params.command, "the-command-for-code-action-1");
10531            let fake = fake.clone();
10532            let lock = lock.clone();
10533            async move {
10534                lock.lock().await;
10535                fake.server
10536                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10537                        label: None,
10538                        edit: lsp::WorkspaceEdit {
10539                            changes: Some(
10540                                [(
10541                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10542                                    vec![lsp::TextEdit {
10543                                        range: lsp::Range::new(
10544                                            lsp::Position::new(0, 0),
10545                                            lsp::Position::new(0, 0),
10546                                        ),
10547                                        new_text: "applied-code-action-1-command\n".into(),
10548                                    }],
10549                                )]
10550                                .into_iter()
10551                                .collect(),
10552                            ),
10553                            ..Default::default()
10554                        },
10555                    })
10556                    .await
10557                    .into_response()
10558                    .unwrap();
10559                Ok(Some(json!(null)))
10560            }
10561        }
10562    });
10563
10564    cx.executor().start_waiting();
10565    editor
10566        .update_in(cx, |editor, window, cx| {
10567            editor.perform_format(
10568                project.clone(),
10569                FormatTrigger::Manual,
10570                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10571                window,
10572                cx,
10573            )
10574        })
10575        .unwrap()
10576        .await;
10577    editor.update(cx, |editor, cx| {
10578        assert_eq!(
10579            editor.text(cx),
10580            r#"
10581                applied-code-action-2-edit
10582                applied-code-action-1-command
10583                applied-code-action-1-edit
10584                applied-formatting
10585                one
10586                two
10587                three
10588            "#
10589            .unindent()
10590        );
10591    });
10592
10593    editor.update_in(cx, |editor, window, cx| {
10594        editor.undo(&Default::default(), window, cx);
10595        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10596    });
10597
10598    // Perform a manual edit while waiting for an LSP command
10599    // that's being run as part of a formatting code action.
10600    let lock_guard = command_lock.lock().await;
10601    let format = editor
10602        .update_in(cx, |editor, window, cx| {
10603            editor.perform_format(
10604                project.clone(),
10605                FormatTrigger::Manual,
10606                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10607                window,
10608                cx,
10609            )
10610        })
10611        .unwrap();
10612    cx.run_until_parked();
10613    editor.update(cx, |editor, cx| {
10614        assert_eq!(
10615            editor.text(cx),
10616            r#"
10617                applied-code-action-1-edit
10618                applied-formatting
10619                one
10620                two
10621                three
10622            "#
10623            .unindent()
10624        );
10625
10626        editor.buffer.update(cx, |buffer, cx| {
10627            let ix = buffer.len(cx);
10628            buffer.edit([(ix..ix, "edited\n")], None, cx);
10629        });
10630    });
10631
10632    // Allow the LSP command to proceed. Because the buffer was edited,
10633    // the second code action will not be run.
10634    drop(lock_guard);
10635    format.await;
10636    editor.update_in(cx, |editor, window, cx| {
10637        assert_eq!(
10638            editor.text(cx),
10639            r#"
10640                applied-code-action-1-command
10641                applied-code-action-1-edit
10642                applied-formatting
10643                one
10644                two
10645                three
10646                edited
10647            "#
10648            .unindent()
10649        );
10650
10651        // The manual edit is undone first, because it is the last thing the user did
10652        // (even though the command completed afterwards).
10653        editor.undo(&Default::default(), window, cx);
10654        assert_eq!(
10655            editor.text(cx),
10656            r#"
10657                applied-code-action-1-command
10658                applied-code-action-1-edit
10659                applied-formatting
10660                one
10661                two
10662                three
10663            "#
10664            .unindent()
10665        );
10666
10667        // All the formatting (including the command, which completed after the manual edit)
10668        // is undone together.
10669        editor.undo(&Default::default(), window, cx);
10670        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10671    });
10672}
10673
10674#[gpui::test]
10675async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10676    init_test(cx, |settings| {
10677        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10678            Formatter::LanguageServer { name: None },
10679        ])))
10680    });
10681
10682    let fs = FakeFs::new(cx.executor());
10683    fs.insert_file(path!("/file.ts"), Default::default()).await;
10684
10685    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10686
10687    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10688    language_registry.add(Arc::new(Language::new(
10689        LanguageConfig {
10690            name: "TypeScript".into(),
10691            matcher: LanguageMatcher {
10692                path_suffixes: vec!["ts".to_string()],
10693                ..Default::default()
10694            },
10695            ..LanguageConfig::default()
10696        },
10697        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10698    )));
10699    update_test_language_settings(cx, |settings| {
10700        settings.defaults.prettier = Some(PrettierSettings {
10701            allowed: true,
10702            ..PrettierSettings::default()
10703        });
10704    });
10705    let mut fake_servers = language_registry.register_fake_lsp(
10706        "TypeScript",
10707        FakeLspAdapter {
10708            capabilities: lsp::ServerCapabilities {
10709                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10710                ..Default::default()
10711            },
10712            ..Default::default()
10713        },
10714    );
10715
10716    let buffer = project
10717        .update(cx, |project, cx| {
10718            project.open_local_buffer(path!("/file.ts"), cx)
10719        })
10720        .await
10721        .unwrap();
10722
10723    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10724    let (editor, cx) = cx.add_window_view(|window, cx| {
10725        build_editor_with_project(project.clone(), buffer, window, cx)
10726    });
10727    editor.update_in(cx, |editor, window, cx| {
10728        editor.set_text(
10729            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10730            window,
10731            cx,
10732        )
10733    });
10734
10735    cx.executor().start_waiting();
10736    let fake_server = fake_servers.next().await.unwrap();
10737
10738    let format = editor
10739        .update_in(cx, |editor, window, cx| {
10740            editor.perform_code_action_kind(
10741                project.clone(),
10742                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10743                window,
10744                cx,
10745            )
10746        })
10747        .unwrap();
10748    fake_server
10749        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10750            assert_eq!(
10751                params.text_document.uri,
10752                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10753            );
10754            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10755                lsp::CodeAction {
10756                    title: "Organize Imports".to_string(),
10757                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10758                    edit: Some(lsp::WorkspaceEdit {
10759                        changes: Some(
10760                            [(
10761                                params.text_document.uri.clone(),
10762                                vec![lsp::TextEdit::new(
10763                                    lsp::Range::new(
10764                                        lsp::Position::new(1, 0),
10765                                        lsp::Position::new(2, 0),
10766                                    ),
10767                                    "".to_string(),
10768                                )],
10769                            )]
10770                            .into_iter()
10771                            .collect(),
10772                        ),
10773                        ..Default::default()
10774                    }),
10775                    ..Default::default()
10776                },
10777            )]))
10778        })
10779        .next()
10780        .await;
10781    cx.executor().start_waiting();
10782    format.await;
10783    assert_eq!(
10784        editor.update(cx, |editor, cx| editor.text(cx)),
10785        "import { a } from 'module';\n\nconst x = a;\n"
10786    );
10787
10788    editor.update_in(cx, |editor, window, cx| {
10789        editor.set_text(
10790            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10791            window,
10792            cx,
10793        )
10794    });
10795    // Ensure we don't lock if code action hangs.
10796    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10797        move |params, _| async move {
10798            assert_eq!(
10799                params.text_document.uri,
10800                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10801            );
10802            futures::future::pending::<()>().await;
10803            unreachable!()
10804        },
10805    );
10806    let format = editor
10807        .update_in(cx, |editor, window, cx| {
10808            editor.perform_code_action_kind(
10809                project,
10810                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10811                window,
10812                cx,
10813            )
10814        })
10815        .unwrap();
10816    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10817    cx.executor().start_waiting();
10818    format.await;
10819    assert_eq!(
10820        editor.update(cx, |editor, cx| editor.text(cx)),
10821        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10822    );
10823}
10824
10825#[gpui::test]
10826async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10827    init_test(cx, |_| {});
10828
10829    let mut cx = EditorLspTestContext::new_rust(
10830        lsp::ServerCapabilities {
10831            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10832            ..Default::default()
10833        },
10834        cx,
10835    )
10836    .await;
10837
10838    cx.set_state(indoc! {"
10839        one.twoˇ
10840    "});
10841
10842    // The format request takes a long time. When it completes, it inserts
10843    // a newline and an indent before the `.`
10844    cx.lsp
10845        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10846            let executor = cx.background_executor().clone();
10847            async move {
10848                executor.timer(Duration::from_millis(100)).await;
10849                Ok(Some(vec![lsp::TextEdit {
10850                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10851                    new_text: "\n    ".into(),
10852                }]))
10853            }
10854        });
10855
10856    // Submit a format request.
10857    let format_1 = cx
10858        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10859        .unwrap();
10860    cx.executor().run_until_parked();
10861
10862    // Submit a second format request.
10863    let format_2 = cx
10864        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10865        .unwrap();
10866    cx.executor().run_until_parked();
10867
10868    // Wait for both format requests to complete
10869    cx.executor().advance_clock(Duration::from_millis(200));
10870    cx.executor().start_waiting();
10871    format_1.await.unwrap();
10872    cx.executor().start_waiting();
10873    format_2.await.unwrap();
10874
10875    // The formatting edits only happens once.
10876    cx.assert_editor_state(indoc! {"
10877        one
10878            .twoˇ
10879    "});
10880}
10881
10882#[gpui::test]
10883async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10884    init_test(cx, |settings| {
10885        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10886    });
10887
10888    let mut cx = EditorLspTestContext::new_rust(
10889        lsp::ServerCapabilities {
10890            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10891            ..Default::default()
10892        },
10893        cx,
10894    )
10895    .await;
10896
10897    // Set up a buffer white some trailing whitespace and no trailing newline.
10898    cx.set_state(
10899        &[
10900            "one ",   //
10901            "twoˇ",   //
10902            "three ", //
10903            "four",   //
10904        ]
10905        .join("\n"),
10906    );
10907
10908    // Submit a format request.
10909    let format = cx
10910        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10911        .unwrap();
10912
10913    // Record which buffer changes have been sent to the language server
10914    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10915    cx.lsp
10916        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10917            let buffer_changes = buffer_changes.clone();
10918            move |params, _| {
10919                buffer_changes.lock().extend(
10920                    params
10921                        .content_changes
10922                        .into_iter()
10923                        .map(|e| (e.range.unwrap(), e.text)),
10924                );
10925            }
10926        });
10927
10928    // Handle formatting requests to the language server.
10929    cx.lsp
10930        .set_request_handler::<lsp::request::Formatting, _, _>({
10931            let buffer_changes = buffer_changes.clone();
10932            move |_, _| {
10933                // When formatting is requested, trailing whitespace has already been stripped,
10934                // and the trailing newline has already been added.
10935                assert_eq!(
10936                    &buffer_changes.lock()[1..],
10937                    &[
10938                        (
10939                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10940                            "".into()
10941                        ),
10942                        (
10943                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10944                            "".into()
10945                        ),
10946                        (
10947                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10948                            "\n".into()
10949                        ),
10950                    ]
10951                );
10952
10953                // Insert blank lines between each line of the buffer.
10954                async move {
10955                    Ok(Some(vec![
10956                        lsp::TextEdit {
10957                            range: lsp::Range::new(
10958                                lsp::Position::new(1, 0),
10959                                lsp::Position::new(1, 0),
10960                            ),
10961                            new_text: "\n".into(),
10962                        },
10963                        lsp::TextEdit {
10964                            range: lsp::Range::new(
10965                                lsp::Position::new(2, 0),
10966                                lsp::Position::new(2, 0),
10967                            ),
10968                            new_text: "\n".into(),
10969                        },
10970                    ]))
10971                }
10972            }
10973        });
10974
10975    // After formatting the buffer, the trailing whitespace is stripped,
10976    // a newline is appended, and the edits provided by the language server
10977    // have been applied.
10978    format.await.unwrap();
10979    cx.assert_editor_state(
10980        &[
10981            "one",   //
10982            "",      //
10983            "twoˇ",  //
10984            "",      //
10985            "three", //
10986            "four",  //
10987            "",      //
10988        ]
10989        .join("\n"),
10990    );
10991
10992    // Undoing the formatting undoes the trailing whitespace removal, the
10993    // trailing newline, and the LSP edits.
10994    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10995    cx.assert_editor_state(
10996        &[
10997            "one ",   //
10998            "twoˇ",   //
10999            "three ", //
11000            "four",   //
11001        ]
11002        .join("\n"),
11003    );
11004}
11005
11006#[gpui::test]
11007async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11008    cx: &mut TestAppContext,
11009) {
11010    init_test(cx, |_| {});
11011
11012    cx.update(|cx| {
11013        cx.update_global::<SettingsStore, _>(|settings, cx| {
11014            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11015                settings.auto_signature_help = Some(true);
11016            });
11017        });
11018    });
11019
11020    let mut cx = EditorLspTestContext::new_rust(
11021        lsp::ServerCapabilities {
11022            signature_help_provider: Some(lsp::SignatureHelpOptions {
11023                ..Default::default()
11024            }),
11025            ..Default::default()
11026        },
11027        cx,
11028    )
11029    .await;
11030
11031    let language = Language::new(
11032        LanguageConfig {
11033            name: "Rust".into(),
11034            brackets: BracketPairConfig {
11035                pairs: vec![
11036                    BracketPair {
11037                        start: "{".to_string(),
11038                        end: "}".to_string(),
11039                        close: true,
11040                        surround: true,
11041                        newline: true,
11042                    },
11043                    BracketPair {
11044                        start: "(".to_string(),
11045                        end: ")".to_string(),
11046                        close: true,
11047                        surround: true,
11048                        newline: true,
11049                    },
11050                    BracketPair {
11051                        start: "/*".to_string(),
11052                        end: " */".to_string(),
11053                        close: true,
11054                        surround: true,
11055                        newline: true,
11056                    },
11057                    BracketPair {
11058                        start: "[".to_string(),
11059                        end: "]".to_string(),
11060                        close: false,
11061                        surround: false,
11062                        newline: true,
11063                    },
11064                    BracketPair {
11065                        start: "\"".to_string(),
11066                        end: "\"".to_string(),
11067                        close: true,
11068                        surround: true,
11069                        newline: false,
11070                    },
11071                    BracketPair {
11072                        start: "<".to_string(),
11073                        end: ">".to_string(),
11074                        close: false,
11075                        surround: true,
11076                        newline: true,
11077                    },
11078                ],
11079                ..Default::default()
11080            },
11081            autoclose_before: "})]".to_string(),
11082            ..Default::default()
11083        },
11084        Some(tree_sitter_rust::LANGUAGE.into()),
11085    );
11086    let language = Arc::new(language);
11087
11088    cx.language_registry().add(language.clone());
11089    cx.update_buffer(|buffer, cx| {
11090        buffer.set_language(Some(language), cx);
11091    });
11092
11093    cx.set_state(
11094        &r#"
11095            fn main() {
11096                sampleˇ
11097            }
11098        "#
11099        .unindent(),
11100    );
11101
11102    cx.update_editor(|editor, window, cx| {
11103        editor.handle_input("(", window, cx);
11104    });
11105    cx.assert_editor_state(
11106        &"
11107            fn main() {
11108                sample(ˇ)
11109            }
11110        "
11111        .unindent(),
11112    );
11113
11114    let mocked_response = lsp::SignatureHelp {
11115        signatures: vec![lsp::SignatureInformation {
11116            label: "fn sample(param1: u8, param2: u8)".to_string(),
11117            documentation: None,
11118            parameters: Some(vec![
11119                lsp::ParameterInformation {
11120                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11121                    documentation: None,
11122                },
11123                lsp::ParameterInformation {
11124                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11125                    documentation: None,
11126                },
11127            ]),
11128            active_parameter: None,
11129        }],
11130        active_signature: Some(0),
11131        active_parameter: Some(0),
11132    };
11133    handle_signature_help_request(&mut cx, mocked_response).await;
11134
11135    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11136        .await;
11137
11138    cx.editor(|editor, _, _| {
11139        let signature_help_state = editor.signature_help_state.popover().cloned();
11140        let signature = signature_help_state.unwrap();
11141        assert_eq!(
11142            signature.signatures[signature.current_signature].label,
11143            "fn sample(param1: u8, param2: u8)"
11144        );
11145    });
11146}
11147
11148#[gpui::test]
11149async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11150    init_test(cx, |_| {});
11151
11152    cx.update(|cx| {
11153        cx.update_global::<SettingsStore, _>(|settings, cx| {
11154            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11155                settings.auto_signature_help = Some(false);
11156                settings.show_signature_help_after_edits = Some(false);
11157            });
11158        });
11159    });
11160
11161    let mut cx = EditorLspTestContext::new_rust(
11162        lsp::ServerCapabilities {
11163            signature_help_provider: Some(lsp::SignatureHelpOptions {
11164                ..Default::default()
11165            }),
11166            ..Default::default()
11167        },
11168        cx,
11169    )
11170    .await;
11171
11172    let language = Language::new(
11173        LanguageConfig {
11174            name: "Rust".into(),
11175            brackets: BracketPairConfig {
11176                pairs: vec![
11177                    BracketPair {
11178                        start: "{".to_string(),
11179                        end: "}".to_string(),
11180                        close: true,
11181                        surround: true,
11182                        newline: true,
11183                    },
11184                    BracketPair {
11185                        start: "(".to_string(),
11186                        end: ")".to_string(),
11187                        close: true,
11188                        surround: true,
11189                        newline: true,
11190                    },
11191                    BracketPair {
11192                        start: "/*".to_string(),
11193                        end: " */".to_string(),
11194                        close: true,
11195                        surround: true,
11196                        newline: true,
11197                    },
11198                    BracketPair {
11199                        start: "[".to_string(),
11200                        end: "]".to_string(),
11201                        close: false,
11202                        surround: false,
11203                        newline: true,
11204                    },
11205                    BracketPair {
11206                        start: "\"".to_string(),
11207                        end: "\"".to_string(),
11208                        close: true,
11209                        surround: true,
11210                        newline: false,
11211                    },
11212                    BracketPair {
11213                        start: "<".to_string(),
11214                        end: ">".to_string(),
11215                        close: false,
11216                        surround: true,
11217                        newline: true,
11218                    },
11219                ],
11220                ..Default::default()
11221            },
11222            autoclose_before: "})]".to_string(),
11223            ..Default::default()
11224        },
11225        Some(tree_sitter_rust::LANGUAGE.into()),
11226    );
11227    let language = Arc::new(language);
11228
11229    cx.language_registry().add(language.clone());
11230    cx.update_buffer(|buffer, cx| {
11231        buffer.set_language(Some(language), cx);
11232    });
11233
11234    // Ensure that signature_help is not called when no signature help is enabled.
11235    cx.set_state(
11236        &r#"
11237            fn main() {
11238                sampleˇ
11239            }
11240        "#
11241        .unindent(),
11242    );
11243    cx.update_editor(|editor, window, cx| {
11244        editor.handle_input("(", window, cx);
11245    });
11246    cx.assert_editor_state(
11247        &"
11248            fn main() {
11249                sample(ˇ)
11250            }
11251        "
11252        .unindent(),
11253    );
11254    cx.editor(|editor, _, _| {
11255        assert!(editor.signature_help_state.task().is_none());
11256    });
11257
11258    let mocked_response = lsp::SignatureHelp {
11259        signatures: vec![lsp::SignatureInformation {
11260            label: "fn sample(param1: u8, param2: u8)".to_string(),
11261            documentation: None,
11262            parameters: Some(vec![
11263                lsp::ParameterInformation {
11264                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11265                    documentation: None,
11266                },
11267                lsp::ParameterInformation {
11268                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11269                    documentation: None,
11270                },
11271            ]),
11272            active_parameter: None,
11273        }],
11274        active_signature: Some(0),
11275        active_parameter: Some(0),
11276    };
11277
11278    // Ensure that signature_help is called when enabled afte edits
11279    cx.update(|_, cx| {
11280        cx.update_global::<SettingsStore, _>(|settings, cx| {
11281            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11282                settings.auto_signature_help = Some(false);
11283                settings.show_signature_help_after_edits = Some(true);
11284            });
11285        });
11286    });
11287    cx.set_state(
11288        &r#"
11289            fn main() {
11290                sampleˇ
11291            }
11292        "#
11293        .unindent(),
11294    );
11295    cx.update_editor(|editor, window, cx| {
11296        editor.handle_input("(", window, cx);
11297    });
11298    cx.assert_editor_state(
11299        &"
11300            fn main() {
11301                sample(ˇ)
11302            }
11303        "
11304        .unindent(),
11305    );
11306    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11307    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11308        .await;
11309    cx.update_editor(|editor, _, _| {
11310        let signature_help_state = editor.signature_help_state.popover().cloned();
11311        assert!(signature_help_state.is_some());
11312        let signature = signature_help_state.unwrap();
11313        assert_eq!(
11314            signature.signatures[signature.current_signature].label,
11315            "fn sample(param1: u8, param2: u8)"
11316        );
11317        editor.signature_help_state = SignatureHelpState::default();
11318    });
11319
11320    // Ensure that signature_help is called when auto signature help override is enabled
11321    cx.update(|_, cx| {
11322        cx.update_global::<SettingsStore, _>(|settings, cx| {
11323            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11324                settings.auto_signature_help = Some(true);
11325                settings.show_signature_help_after_edits = Some(false);
11326            });
11327        });
11328    });
11329    cx.set_state(
11330        &r#"
11331            fn main() {
11332                sampleˇ
11333            }
11334        "#
11335        .unindent(),
11336    );
11337    cx.update_editor(|editor, window, cx| {
11338        editor.handle_input("(", window, cx);
11339    });
11340    cx.assert_editor_state(
11341        &"
11342            fn main() {
11343                sample(ˇ)
11344            }
11345        "
11346        .unindent(),
11347    );
11348    handle_signature_help_request(&mut cx, mocked_response).await;
11349    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11350        .await;
11351    cx.editor(|editor, _, _| {
11352        let signature_help_state = editor.signature_help_state.popover().cloned();
11353        assert!(signature_help_state.is_some());
11354        let signature = signature_help_state.unwrap();
11355        assert_eq!(
11356            signature.signatures[signature.current_signature].label,
11357            "fn sample(param1: u8, param2: u8)"
11358        );
11359    });
11360}
11361
11362#[gpui::test]
11363async fn test_signature_help(cx: &mut TestAppContext) {
11364    init_test(cx, |_| {});
11365    cx.update(|cx| {
11366        cx.update_global::<SettingsStore, _>(|settings, cx| {
11367            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11368                settings.auto_signature_help = Some(true);
11369            });
11370        });
11371    });
11372
11373    let mut cx = EditorLspTestContext::new_rust(
11374        lsp::ServerCapabilities {
11375            signature_help_provider: Some(lsp::SignatureHelpOptions {
11376                ..Default::default()
11377            }),
11378            ..Default::default()
11379        },
11380        cx,
11381    )
11382    .await;
11383
11384    // A test that directly calls `show_signature_help`
11385    cx.update_editor(|editor, window, cx| {
11386        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11387    });
11388
11389    let mocked_response = lsp::SignatureHelp {
11390        signatures: vec![lsp::SignatureInformation {
11391            label: "fn sample(param1: u8, param2: u8)".to_string(),
11392            documentation: None,
11393            parameters: Some(vec![
11394                lsp::ParameterInformation {
11395                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11396                    documentation: None,
11397                },
11398                lsp::ParameterInformation {
11399                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11400                    documentation: None,
11401                },
11402            ]),
11403            active_parameter: None,
11404        }],
11405        active_signature: Some(0),
11406        active_parameter: Some(0),
11407    };
11408    handle_signature_help_request(&mut cx, mocked_response).await;
11409
11410    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11411        .await;
11412
11413    cx.editor(|editor, _, _| {
11414        let signature_help_state = editor.signature_help_state.popover().cloned();
11415        assert!(signature_help_state.is_some());
11416        let signature = signature_help_state.unwrap();
11417        assert_eq!(
11418            signature.signatures[signature.current_signature].label,
11419            "fn sample(param1: u8, param2: u8)"
11420        );
11421    });
11422
11423    // When exiting outside from inside the brackets, `signature_help` is closed.
11424    cx.set_state(indoc! {"
11425        fn main() {
11426            sample(ˇ);
11427        }
11428
11429        fn sample(param1: u8, param2: u8) {}
11430    "});
11431
11432    cx.update_editor(|editor, window, cx| {
11433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11434            s.select_ranges([0..0])
11435        });
11436    });
11437
11438    let mocked_response = lsp::SignatureHelp {
11439        signatures: Vec::new(),
11440        active_signature: None,
11441        active_parameter: None,
11442    };
11443    handle_signature_help_request(&mut cx, mocked_response).await;
11444
11445    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11446        .await;
11447
11448    cx.editor(|editor, _, _| {
11449        assert!(!editor.signature_help_state.is_shown());
11450    });
11451
11452    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11453    cx.set_state(indoc! {"
11454        fn main() {
11455            sample(ˇ);
11456        }
11457
11458        fn sample(param1: u8, param2: u8) {}
11459    "});
11460
11461    let mocked_response = lsp::SignatureHelp {
11462        signatures: vec![lsp::SignatureInformation {
11463            label: "fn sample(param1: u8, param2: u8)".to_string(),
11464            documentation: None,
11465            parameters: Some(vec![
11466                lsp::ParameterInformation {
11467                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11468                    documentation: None,
11469                },
11470                lsp::ParameterInformation {
11471                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11472                    documentation: None,
11473                },
11474            ]),
11475            active_parameter: None,
11476        }],
11477        active_signature: Some(0),
11478        active_parameter: Some(0),
11479    };
11480    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11481    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11482        .await;
11483    cx.editor(|editor, _, _| {
11484        assert!(editor.signature_help_state.is_shown());
11485    });
11486
11487    // Restore the popover with more parameter input
11488    cx.set_state(indoc! {"
11489        fn main() {
11490            sample(param1, param2ˇ);
11491        }
11492
11493        fn sample(param1: u8, param2: u8) {}
11494    "});
11495
11496    let mocked_response = lsp::SignatureHelp {
11497        signatures: vec![lsp::SignatureInformation {
11498            label: "fn sample(param1: u8, param2: u8)".to_string(),
11499            documentation: None,
11500            parameters: Some(vec![
11501                lsp::ParameterInformation {
11502                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11503                    documentation: None,
11504                },
11505                lsp::ParameterInformation {
11506                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11507                    documentation: None,
11508                },
11509            ]),
11510            active_parameter: None,
11511        }],
11512        active_signature: Some(0),
11513        active_parameter: Some(1),
11514    };
11515    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11516    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11517        .await;
11518
11519    // When selecting a range, the popover is gone.
11520    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11521    cx.update_editor(|editor, window, cx| {
11522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11523            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11524        })
11525    });
11526    cx.assert_editor_state(indoc! {"
11527        fn main() {
11528            sample(param1, «ˇparam2»);
11529        }
11530
11531        fn sample(param1: u8, param2: u8) {}
11532    "});
11533    cx.editor(|editor, _, _| {
11534        assert!(!editor.signature_help_state.is_shown());
11535    });
11536
11537    // When unselecting again, the popover is back if within the brackets.
11538    cx.update_editor(|editor, window, cx| {
11539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11540            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11541        })
11542    });
11543    cx.assert_editor_state(indoc! {"
11544        fn main() {
11545            sample(param1, ˇparam2);
11546        }
11547
11548        fn sample(param1: u8, param2: u8) {}
11549    "});
11550    handle_signature_help_request(&mut cx, mocked_response).await;
11551    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11552        .await;
11553    cx.editor(|editor, _, _| {
11554        assert!(editor.signature_help_state.is_shown());
11555    });
11556
11557    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11558    cx.update_editor(|editor, window, cx| {
11559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11560            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11561            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11562        })
11563    });
11564    cx.assert_editor_state(indoc! {"
11565        fn main() {
11566            sample(param1, ˇparam2);
11567        }
11568
11569        fn sample(param1: u8, param2: u8) {}
11570    "});
11571
11572    let mocked_response = lsp::SignatureHelp {
11573        signatures: vec![lsp::SignatureInformation {
11574            label: "fn sample(param1: u8, param2: u8)".to_string(),
11575            documentation: None,
11576            parameters: Some(vec![
11577                lsp::ParameterInformation {
11578                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11579                    documentation: None,
11580                },
11581                lsp::ParameterInformation {
11582                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11583                    documentation: None,
11584                },
11585            ]),
11586            active_parameter: None,
11587        }],
11588        active_signature: Some(0),
11589        active_parameter: Some(1),
11590    };
11591    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11592    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11593        .await;
11594    cx.update_editor(|editor, _, cx| {
11595        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11596    });
11597    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11598        .await;
11599    cx.update_editor(|editor, window, cx| {
11600        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11601            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11602        })
11603    });
11604    cx.assert_editor_state(indoc! {"
11605        fn main() {
11606            sample(param1, «ˇparam2»);
11607        }
11608
11609        fn sample(param1: u8, param2: u8) {}
11610    "});
11611    cx.update_editor(|editor, window, cx| {
11612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11613            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11614        })
11615    });
11616    cx.assert_editor_state(indoc! {"
11617        fn main() {
11618            sample(param1, ˇparam2);
11619        }
11620
11621        fn sample(param1: u8, param2: u8) {}
11622    "});
11623    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11624        .await;
11625}
11626
11627#[gpui::test]
11628async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11629    init_test(cx, |_| {});
11630
11631    let mut cx = EditorLspTestContext::new_rust(
11632        lsp::ServerCapabilities {
11633            signature_help_provider: Some(lsp::SignatureHelpOptions {
11634                ..Default::default()
11635            }),
11636            ..Default::default()
11637        },
11638        cx,
11639    )
11640    .await;
11641
11642    cx.set_state(indoc! {"
11643        fn main() {
11644            overloadedˇ
11645        }
11646    "});
11647
11648    cx.update_editor(|editor, window, cx| {
11649        editor.handle_input("(", window, cx);
11650        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11651    });
11652
11653    // Mock response with 3 signatures
11654    let mocked_response = lsp::SignatureHelp {
11655        signatures: vec![
11656            lsp::SignatureInformation {
11657                label: "fn overloaded(x: i32)".to_string(),
11658                documentation: None,
11659                parameters: Some(vec![lsp::ParameterInformation {
11660                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11661                    documentation: None,
11662                }]),
11663                active_parameter: None,
11664            },
11665            lsp::SignatureInformation {
11666                label: "fn overloaded(x: i32, y: i32)".to_string(),
11667                documentation: None,
11668                parameters: Some(vec![
11669                    lsp::ParameterInformation {
11670                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11671                        documentation: None,
11672                    },
11673                    lsp::ParameterInformation {
11674                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11675                        documentation: None,
11676                    },
11677                ]),
11678                active_parameter: None,
11679            },
11680            lsp::SignatureInformation {
11681                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11682                documentation: None,
11683                parameters: Some(vec![
11684                    lsp::ParameterInformation {
11685                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11686                        documentation: None,
11687                    },
11688                    lsp::ParameterInformation {
11689                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11690                        documentation: None,
11691                    },
11692                    lsp::ParameterInformation {
11693                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11694                        documentation: None,
11695                    },
11696                ]),
11697                active_parameter: None,
11698            },
11699        ],
11700        active_signature: Some(1),
11701        active_parameter: Some(0),
11702    };
11703    handle_signature_help_request(&mut cx, mocked_response).await;
11704
11705    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11706        .await;
11707
11708    // Verify we have multiple signatures and the right one is selected
11709    cx.editor(|editor, _, _| {
11710        let popover = editor.signature_help_state.popover().cloned().unwrap();
11711        assert_eq!(popover.signatures.len(), 3);
11712        // active_signature was 1, so that should be the current
11713        assert_eq!(popover.current_signature, 1);
11714        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11715        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11716        assert_eq!(
11717            popover.signatures[2].label,
11718            "fn overloaded(x: i32, y: i32, z: i32)"
11719        );
11720    });
11721
11722    // Test navigation functionality
11723    cx.update_editor(|editor, window, cx| {
11724        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11725    });
11726
11727    cx.editor(|editor, _, _| {
11728        let popover = editor.signature_help_state.popover().cloned().unwrap();
11729        assert_eq!(popover.current_signature, 2);
11730    });
11731
11732    // Test wrap around
11733    cx.update_editor(|editor, window, cx| {
11734        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11735    });
11736
11737    cx.editor(|editor, _, _| {
11738        let popover = editor.signature_help_state.popover().cloned().unwrap();
11739        assert_eq!(popover.current_signature, 0);
11740    });
11741
11742    // Test previous navigation
11743    cx.update_editor(|editor, window, cx| {
11744        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11745    });
11746
11747    cx.editor(|editor, _, _| {
11748        let popover = editor.signature_help_state.popover().cloned().unwrap();
11749        assert_eq!(popover.current_signature, 2);
11750    });
11751}
11752
11753#[gpui::test]
11754async fn test_completion_mode(cx: &mut TestAppContext) {
11755    init_test(cx, |_| {});
11756    let mut cx = EditorLspTestContext::new_rust(
11757        lsp::ServerCapabilities {
11758            completion_provider: Some(lsp::CompletionOptions {
11759                resolve_provider: Some(true),
11760                ..Default::default()
11761            }),
11762            ..Default::default()
11763        },
11764        cx,
11765    )
11766    .await;
11767
11768    struct Run {
11769        run_description: &'static str,
11770        initial_state: String,
11771        buffer_marked_text: String,
11772        completion_label: &'static str,
11773        completion_text: &'static str,
11774        expected_with_insert_mode: String,
11775        expected_with_replace_mode: String,
11776        expected_with_replace_subsequence_mode: String,
11777        expected_with_replace_suffix_mode: String,
11778    }
11779
11780    let runs = [
11781        Run {
11782            run_description: "Start of word matches completion text",
11783            initial_state: "before ediˇ after".into(),
11784            buffer_marked_text: "before <edi|> after".into(),
11785            completion_label: "editor",
11786            completion_text: "editor",
11787            expected_with_insert_mode: "before editorˇ after".into(),
11788            expected_with_replace_mode: "before editorˇ after".into(),
11789            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11790            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11791        },
11792        Run {
11793            run_description: "Accept same text at the middle of the word",
11794            initial_state: "before ediˇtor after".into(),
11795            buffer_marked_text: "before <edi|tor> after".into(),
11796            completion_label: "editor",
11797            completion_text: "editor",
11798            expected_with_insert_mode: "before editorˇtor after".into(),
11799            expected_with_replace_mode: "before editorˇ after".into(),
11800            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11801            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11802        },
11803        Run {
11804            run_description: "End of word matches completion text -- cursor at end",
11805            initial_state: "before torˇ after".into(),
11806            buffer_marked_text: "before <tor|> after".into(),
11807            completion_label: "editor",
11808            completion_text: "editor",
11809            expected_with_insert_mode: "before editorˇ after".into(),
11810            expected_with_replace_mode: "before editorˇ after".into(),
11811            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11812            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11813        },
11814        Run {
11815            run_description: "End of word matches completion text -- cursor at start",
11816            initial_state: "before ˇtor after".into(),
11817            buffer_marked_text: "before <|tor> after".into(),
11818            completion_label: "editor",
11819            completion_text: "editor",
11820            expected_with_insert_mode: "before editorˇtor after".into(),
11821            expected_with_replace_mode: "before editorˇ after".into(),
11822            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11823            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11824        },
11825        Run {
11826            run_description: "Prepend text containing whitespace",
11827            initial_state: "pˇfield: bool".into(),
11828            buffer_marked_text: "<p|field>: bool".into(),
11829            completion_label: "pub ",
11830            completion_text: "pub ",
11831            expected_with_insert_mode: "pub ˇfield: bool".into(),
11832            expected_with_replace_mode: "pub ˇ: bool".into(),
11833            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11834            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11835        },
11836        Run {
11837            run_description: "Add element to start of list",
11838            initial_state: "[element_ˇelement_2]".into(),
11839            buffer_marked_text: "[<element_|element_2>]".into(),
11840            completion_label: "element_1",
11841            completion_text: "element_1",
11842            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11843            expected_with_replace_mode: "[element_1ˇ]".into(),
11844            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11845            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11846        },
11847        Run {
11848            run_description: "Add element to start of list -- first and second elements are equal",
11849            initial_state: "[elˇelement]".into(),
11850            buffer_marked_text: "[<el|element>]".into(),
11851            completion_label: "element",
11852            completion_text: "element",
11853            expected_with_insert_mode: "[elementˇelement]".into(),
11854            expected_with_replace_mode: "[elementˇ]".into(),
11855            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11856            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11857        },
11858        Run {
11859            run_description: "Ends with matching suffix",
11860            initial_state: "SubˇError".into(),
11861            buffer_marked_text: "<Sub|Error>".into(),
11862            completion_label: "SubscriptionError",
11863            completion_text: "SubscriptionError",
11864            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11865            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11866            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11867            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11868        },
11869        Run {
11870            run_description: "Suffix is a subsequence -- contiguous",
11871            initial_state: "SubˇErr".into(),
11872            buffer_marked_text: "<Sub|Err>".into(),
11873            completion_label: "SubscriptionError",
11874            completion_text: "SubscriptionError",
11875            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11876            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11877            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11878            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11879        },
11880        Run {
11881            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11882            initial_state: "Suˇscrirr".into(),
11883            buffer_marked_text: "<Su|scrirr>".into(),
11884            completion_label: "SubscriptionError",
11885            completion_text: "SubscriptionError",
11886            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11887            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11888            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11889            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11890        },
11891        Run {
11892            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11893            initial_state: "foo(indˇix)".into(),
11894            buffer_marked_text: "foo(<ind|ix>)".into(),
11895            completion_label: "node_index",
11896            completion_text: "node_index",
11897            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11898            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11899            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11900            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11901        },
11902        Run {
11903            run_description: "Replace range ends before cursor - should extend to cursor",
11904            initial_state: "before editˇo after".into(),
11905            buffer_marked_text: "before <{ed}>it|o after".into(),
11906            completion_label: "editor",
11907            completion_text: "editor",
11908            expected_with_insert_mode: "before editorˇo after".into(),
11909            expected_with_replace_mode: "before editorˇo after".into(),
11910            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11911            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11912        },
11913        Run {
11914            run_description: "Uses label for suffix matching",
11915            initial_state: "before ediˇtor after".into(),
11916            buffer_marked_text: "before <edi|tor> after".into(),
11917            completion_label: "editor",
11918            completion_text: "editor()",
11919            expected_with_insert_mode: "before editor()ˇtor after".into(),
11920            expected_with_replace_mode: "before editor()ˇ after".into(),
11921            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11922            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11923        },
11924        Run {
11925            run_description: "Case insensitive subsequence and suffix matching",
11926            initial_state: "before EDiˇtoR after".into(),
11927            buffer_marked_text: "before <EDi|toR> after".into(),
11928            completion_label: "editor",
11929            completion_text: "editor",
11930            expected_with_insert_mode: "before editorˇtoR after".into(),
11931            expected_with_replace_mode: "before editorˇ after".into(),
11932            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11933            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11934        },
11935    ];
11936
11937    for run in runs {
11938        let run_variations = [
11939            (LspInsertMode::Insert, run.expected_with_insert_mode),
11940            (LspInsertMode::Replace, run.expected_with_replace_mode),
11941            (
11942                LspInsertMode::ReplaceSubsequence,
11943                run.expected_with_replace_subsequence_mode,
11944            ),
11945            (
11946                LspInsertMode::ReplaceSuffix,
11947                run.expected_with_replace_suffix_mode,
11948            ),
11949        ];
11950
11951        for (lsp_insert_mode, expected_text) in run_variations {
11952            eprintln!(
11953                "run = {:?}, mode = {lsp_insert_mode:.?}",
11954                run.run_description,
11955            );
11956
11957            update_test_language_settings(&mut cx, |settings| {
11958                settings.defaults.completions = Some(CompletionSettings {
11959                    lsp_insert_mode,
11960                    words: WordsCompletionMode::Disabled,
11961                    lsp: true,
11962                    lsp_fetch_timeout_ms: 0,
11963                });
11964            });
11965
11966            cx.set_state(&run.initial_state);
11967            cx.update_editor(|editor, window, cx| {
11968                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11969            });
11970
11971            let counter = Arc::new(AtomicUsize::new(0));
11972            handle_completion_request_with_insert_and_replace(
11973                &mut cx,
11974                &run.buffer_marked_text,
11975                vec![(run.completion_label, run.completion_text)],
11976                counter.clone(),
11977            )
11978            .await;
11979            cx.condition(|editor, _| editor.context_menu_visible())
11980                .await;
11981            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11982
11983            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11984                editor
11985                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11986                    .unwrap()
11987            });
11988            cx.assert_editor_state(&expected_text);
11989            handle_resolve_completion_request(&mut cx, None).await;
11990            apply_additional_edits.await.unwrap();
11991        }
11992    }
11993}
11994
11995#[gpui::test]
11996async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11997    init_test(cx, |_| {});
11998    let mut cx = EditorLspTestContext::new_rust(
11999        lsp::ServerCapabilities {
12000            completion_provider: Some(lsp::CompletionOptions {
12001                resolve_provider: Some(true),
12002                ..Default::default()
12003            }),
12004            ..Default::default()
12005        },
12006        cx,
12007    )
12008    .await;
12009
12010    let initial_state = "SubˇError";
12011    let buffer_marked_text = "<Sub|Error>";
12012    let completion_text = "SubscriptionError";
12013    let expected_with_insert_mode = "SubscriptionErrorˇError";
12014    let expected_with_replace_mode = "SubscriptionErrorˇ";
12015
12016    update_test_language_settings(&mut cx, |settings| {
12017        settings.defaults.completions = Some(CompletionSettings {
12018            words: WordsCompletionMode::Disabled,
12019            // set the opposite here to ensure that the action is overriding the default behavior
12020            lsp_insert_mode: LspInsertMode::Insert,
12021            lsp: true,
12022            lsp_fetch_timeout_ms: 0,
12023        });
12024    });
12025
12026    cx.set_state(initial_state);
12027    cx.update_editor(|editor, window, cx| {
12028        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12029    });
12030
12031    let counter = Arc::new(AtomicUsize::new(0));
12032    handle_completion_request_with_insert_and_replace(
12033        &mut cx,
12034        &buffer_marked_text,
12035        vec![(completion_text, completion_text)],
12036        counter.clone(),
12037    )
12038    .await;
12039    cx.condition(|editor, _| editor.context_menu_visible())
12040        .await;
12041    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12042
12043    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12044        editor
12045            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12046            .unwrap()
12047    });
12048    cx.assert_editor_state(&expected_with_replace_mode);
12049    handle_resolve_completion_request(&mut cx, None).await;
12050    apply_additional_edits.await.unwrap();
12051
12052    update_test_language_settings(&mut cx, |settings| {
12053        settings.defaults.completions = Some(CompletionSettings {
12054            words: WordsCompletionMode::Disabled,
12055            // set the opposite here to ensure that the action is overriding the default behavior
12056            lsp_insert_mode: LspInsertMode::Replace,
12057            lsp: true,
12058            lsp_fetch_timeout_ms: 0,
12059        });
12060    });
12061
12062    cx.set_state(initial_state);
12063    cx.update_editor(|editor, window, cx| {
12064        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12065    });
12066    handle_completion_request_with_insert_and_replace(
12067        &mut cx,
12068        &buffer_marked_text,
12069        vec![(completion_text, completion_text)],
12070        counter.clone(),
12071    )
12072    .await;
12073    cx.condition(|editor, _| editor.context_menu_visible())
12074        .await;
12075    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12076
12077    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12078        editor
12079            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12080            .unwrap()
12081    });
12082    cx.assert_editor_state(&expected_with_insert_mode);
12083    handle_resolve_completion_request(&mut cx, None).await;
12084    apply_additional_edits.await.unwrap();
12085}
12086
12087#[gpui::test]
12088async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12089    init_test(cx, |_| {});
12090    let mut cx = EditorLspTestContext::new_rust(
12091        lsp::ServerCapabilities {
12092            completion_provider: Some(lsp::CompletionOptions {
12093                resolve_provider: Some(true),
12094                ..Default::default()
12095            }),
12096            ..Default::default()
12097        },
12098        cx,
12099    )
12100    .await;
12101
12102    // scenario: surrounding text matches completion text
12103    let completion_text = "to_offset";
12104    let initial_state = indoc! {"
12105        1. buf.to_offˇsuffix
12106        2. buf.to_offˇsuf
12107        3. buf.to_offˇfix
12108        4. buf.to_offˇ
12109        5. into_offˇensive
12110        6. ˇsuffix
12111        7. let ˇ //
12112        8. aaˇzz
12113        9. buf.to_off«zzzzzˇ»suffix
12114        10. buf.«ˇzzzzz»suffix
12115        11. to_off«ˇzzzzz»
12116
12117        buf.to_offˇsuffix  // newest cursor
12118    "};
12119    let completion_marked_buffer = indoc! {"
12120        1. buf.to_offsuffix
12121        2. buf.to_offsuf
12122        3. buf.to_offfix
12123        4. buf.to_off
12124        5. into_offensive
12125        6. suffix
12126        7. let  //
12127        8. aazz
12128        9. buf.to_offzzzzzsuffix
12129        10. buf.zzzzzsuffix
12130        11. to_offzzzzz
12131
12132        buf.<to_off|suffix>  // newest cursor
12133    "};
12134    let expected = indoc! {"
12135        1. buf.to_offsetˇ
12136        2. buf.to_offsetˇsuf
12137        3. buf.to_offsetˇfix
12138        4. buf.to_offsetˇ
12139        5. into_offsetˇensive
12140        6. to_offsetˇsuffix
12141        7. let to_offsetˇ //
12142        8. aato_offsetˇzz
12143        9. buf.to_offsetˇ
12144        10. buf.to_offsetˇsuffix
12145        11. to_offsetˇ
12146
12147        buf.to_offsetˇ  // newest cursor
12148    "};
12149    cx.set_state(initial_state);
12150    cx.update_editor(|editor, window, cx| {
12151        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12152    });
12153    handle_completion_request_with_insert_and_replace(
12154        &mut cx,
12155        completion_marked_buffer,
12156        vec![(completion_text, completion_text)],
12157        Arc::new(AtomicUsize::new(0)),
12158    )
12159    .await;
12160    cx.condition(|editor, _| editor.context_menu_visible())
12161        .await;
12162    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12163        editor
12164            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12165            .unwrap()
12166    });
12167    cx.assert_editor_state(expected);
12168    handle_resolve_completion_request(&mut cx, None).await;
12169    apply_additional_edits.await.unwrap();
12170
12171    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12172    let completion_text = "foo_and_bar";
12173    let initial_state = indoc! {"
12174        1. ooanbˇ
12175        2. zooanbˇ
12176        3. ooanbˇz
12177        4. zooanbˇz
12178        5. ooanˇ
12179        6. oanbˇ
12180
12181        ooanbˇ
12182    "};
12183    let completion_marked_buffer = indoc! {"
12184        1. ooanb
12185        2. zooanb
12186        3. ooanbz
12187        4. zooanbz
12188        5. ooan
12189        6. oanb
12190
12191        <ooanb|>
12192    "};
12193    let expected = indoc! {"
12194        1. foo_and_barˇ
12195        2. zfoo_and_barˇ
12196        3. foo_and_barˇz
12197        4. zfoo_and_barˇz
12198        5. ooanfoo_and_barˇ
12199        6. oanbfoo_and_barˇ
12200
12201        foo_and_barˇ
12202    "};
12203    cx.set_state(initial_state);
12204    cx.update_editor(|editor, window, cx| {
12205        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12206    });
12207    handle_completion_request_with_insert_and_replace(
12208        &mut cx,
12209        completion_marked_buffer,
12210        vec![(completion_text, completion_text)],
12211        Arc::new(AtomicUsize::new(0)),
12212    )
12213    .await;
12214    cx.condition(|editor, _| editor.context_menu_visible())
12215        .await;
12216    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12217        editor
12218            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12219            .unwrap()
12220    });
12221    cx.assert_editor_state(expected);
12222    handle_resolve_completion_request(&mut cx, None).await;
12223    apply_additional_edits.await.unwrap();
12224
12225    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12226    // (expects the same as if it was inserted at the end)
12227    let completion_text = "foo_and_bar";
12228    let initial_state = indoc! {"
12229        1. ooˇanb
12230        2. zooˇanb
12231        3. ooˇanbz
12232        4. zooˇanbz
12233
12234        ooˇanb
12235    "};
12236    let completion_marked_buffer = indoc! {"
12237        1. ooanb
12238        2. zooanb
12239        3. ooanbz
12240        4. zooanbz
12241
12242        <oo|anb>
12243    "};
12244    let expected = indoc! {"
12245        1. foo_and_barˇ
12246        2. zfoo_and_barˇ
12247        3. foo_and_barˇz
12248        4. zfoo_and_barˇz
12249
12250        foo_and_barˇ
12251    "};
12252    cx.set_state(initial_state);
12253    cx.update_editor(|editor, window, cx| {
12254        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12255    });
12256    handle_completion_request_with_insert_and_replace(
12257        &mut cx,
12258        completion_marked_buffer,
12259        vec![(completion_text, completion_text)],
12260        Arc::new(AtomicUsize::new(0)),
12261    )
12262    .await;
12263    cx.condition(|editor, _| editor.context_menu_visible())
12264        .await;
12265    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12266        editor
12267            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12268            .unwrap()
12269    });
12270    cx.assert_editor_state(expected);
12271    handle_resolve_completion_request(&mut cx, None).await;
12272    apply_additional_edits.await.unwrap();
12273}
12274
12275// This used to crash
12276#[gpui::test]
12277async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12278    init_test(cx, |_| {});
12279
12280    let buffer_text = indoc! {"
12281        fn main() {
12282            10.satu;
12283
12284            //
12285            // separate cursors so they open in different excerpts (manually reproducible)
12286            //
12287
12288            10.satu20;
12289        }
12290    "};
12291    let multibuffer_text_with_selections = indoc! {"
12292        fn main() {
12293            10.satuˇ;
12294
12295            //
12296
12297            //
12298
12299            10.satuˇ20;
12300        }
12301    "};
12302    let expected_multibuffer = indoc! {"
12303        fn main() {
12304            10.saturating_sub()ˇ;
12305
12306            //
12307
12308            //
12309
12310            10.saturating_sub()ˇ;
12311        }
12312    "};
12313
12314    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12315    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12316
12317    let fs = FakeFs::new(cx.executor());
12318    fs.insert_tree(
12319        path!("/a"),
12320        json!({
12321            "main.rs": buffer_text,
12322        }),
12323    )
12324    .await;
12325
12326    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12327    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12328    language_registry.add(rust_lang());
12329    let mut fake_servers = language_registry.register_fake_lsp(
12330        "Rust",
12331        FakeLspAdapter {
12332            capabilities: lsp::ServerCapabilities {
12333                completion_provider: Some(lsp::CompletionOptions {
12334                    resolve_provider: None,
12335                    ..lsp::CompletionOptions::default()
12336                }),
12337                ..lsp::ServerCapabilities::default()
12338            },
12339            ..FakeLspAdapter::default()
12340        },
12341    );
12342    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12343    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12344    let buffer = project
12345        .update(cx, |project, cx| {
12346            project.open_local_buffer(path!("/a/main.rs"), cx)
12347        })
12348        .await
12349        .unwrap();
12350
12351    let multi_buffer = cx.new(|cx| {
12352        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12353        multi_buffer.push_excerpts(
12354            buffer.clone(),
12355            [ExcerptRange::new(0..first_excerpt_end)],
12356            cx,
12357        );
12358        multi_buffer.push_excerpts(
12359            buffer.clone(),
12360            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12361            cx,
12362        );
12363        multi_buffer
12364    });
12365
12366    let editor = workspace
12367        .update(cx, |_, window, cx| {
12368            cx.new(|cx| {
12369                Editor::new(
12370                    EditorMode::Full {
12371                        scale_ui_elements_with_buffer_font_size: false,
12372                        show_active_line_background: false,
12373                        sized_by_content: false,
12374                    },
12375                    multi_buffer.clone(),
12376                    Some(project.clone()),
12377                    window,
12378                    cx,
12379                )
12380            })
12381        })
12382        .unwrap();
12383
12384    let pane = workspace
12385        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12386        .unwrap();
12387    pane.update_in(cx, |pane, window, cx| {
12388        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12389    });
12390
12391    let fake_server = fake_servers.next().await.unwrap();
12392
12393    editor.update_in(cx, |editor, window, cx| {
12394        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12395            s.select_ranges([
12396                Point::new(1, 11)..Point::new(1, 11),
12397                Point::new(7, 11)..Point::new(7, 11),
12398            ])
12399        });
12400
12401        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12402    });
12403
12404    editor.update_in(cx, |editor, window, cx| {
12405        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12406    });
12407
12408    fake_server
12409        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12410            let completion_item = lsp::CompletionItem {
12411                label: "saturating_sub()".into(),
12412                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12413                    lsp::InsertReplaceEdit {
12414                        new_text: "saturating_sub()".to_owned(),
12415                        insert: lsp::Range::new(
12416                            lsp::Position::new(7, 7),
12417                            lsp::Position::new(7, 11),
12418                        ),
12419                        replace: lsp::Range::new(
12420                            lsp::Position::new(7, 7),
12421                            lsp::Position::new(7, 13),
12422                        ),
12423                    },
12424                )),
12425                ..lsp::CompletionItem::default()
12426            };
12427
12428            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12429        })
12430        .next()
12431        .await
12432        .unwrap();
12433
12434    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12435        .await;
12436
12437    editor
12438        .update_in(cx, |editor, window, cx| {
12439            editor
12440                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12441                .unwrap()
12442        })
12443        .await
12444        .unwrap();
12445
12446    editor.update(cx, |editor, cx| {
12447        assert_text_with_selections(editor, expected_multibuffer, cx);
12448    })
12449}
12450
12451#[gpui::test]
12452async fn test_completion(cx: &mut TestAppContext) {
12453    init_test(cx, |_| {});
12454
12455    let mut cx = EditorLspTestContext::new_rust(
12456        lsp::ServerCapabilities {
12457            completion_provider: Some(lsp::CompletionOptions {
12458                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12459                resolve_provider: Some(true),
12460                ..Default::default()
12461            }),
12462            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12463            ..Default::default()
12464        },
12465        cx,
12466    )
12467    .await;
12468    let counter = Arc::new(AtomicUsize::new(0));
12469
12470    cx.set_state(indoc! {"
12471        oneˇ
12472        two
12473        three
12474    "});
12475    cx.simulate_keystroke(".");
12476    handle_completion_request(
12477        indoc! {"
12478            one.|<>
12479            two
12480            three
12481        "},
12482        vec!["first_completion", "second_completion"],
12483        true,
12484        counter.clone(),
12485        &mut cx,
12486    )
12487    .await;
12488    cx.condition(|editor, _| editor.context_menu_visible())
12489        .await;
12490    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12491
12492    let _handler = handle_signature_help_request(
12493        &mut cx,
12494        lsp::SignatureHelp {
12495            signatures: vec![lsp::SignatureInformation {
12496                label: "test signature".to_string(),
12497                documentation: None,
12498                parameters: Some(vec![lsp::ParameterInformation {
12499                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12500                    documentation: None,
12501                }]),
12502                active_parameter: None,
12503            }],
12504            active_signature: None,
12505            active_parameter: None,
12506        },
12507    );
12508    cx.update_editor(|editor, window, cx| {
12509        assert!(
12510            !editor.signature_help_state.is_shown(),
12511            "No signature help was called for"
12512        );
12513        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12514    });
12515    cx.run_until_parked();
12516    cx.update_editor(|editor, _, _| {
12517        assert!(
12518            !editor.signature_help_state.is_shown(),
12519            "No signature help should be shown when completions menu is open"
12520        );
12521    });
12522
12523    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12524        editor.context_menu_next(&Default::default(), window, cx);
12525        editor
12526            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12527            .unwrap()
12528    });
12529    cx.assert_editor_state(indoc! {"
12530        one.second_completionˇ
12531        two
12532        three
12533    "});
12534
12535    handle_resolve_completion_request(
12536        &mut cx,
12537        Some(vec![
12538            (
12539                //This overlaps with the primary completion edit which is
12540                //misbehavior from the LSP spec, test that we filter it out
12541                indoc! {"
12542                    one.second_ˇcompletion
12543                    two
12544                    threeˇ
12545                "},
12546                "overlapping additional edit",
12547            ),
12548            (
12549                indoc! {"
12550                    one.second_completion
12551                    two
12552                    threeˇ
12553                "},
12554                "\nadditional edit",
12555            ),
12556        ]),
12557    )
12558    .await;
12559    apply_additional_edits.await.unwrap();
12560    cx.assert_editor_state(indoc! {"
12561        one.second_completionˇ
12562        two
12563        three
12564        additional edit
12565    "});
12566
12567    cx.set_state(indoc! {"
12568        one.second_completion
12569        twoˇ
12570        threeˇ
12571        additional edit
12572    "});
12573    cx.simulate_keystroke(" ");
12574    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12575    cx.simulate_keystroke("s");
12576    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12577
12578    cx.assert_editor_state(indoc! {"
12579        one.second_completion
12580        two sˇ
12581        three sˇ
12582        additional edit
12583    "});
12584    handle_completion_request(
12585        indoc! {"
12586            one.second_completion
12587            two s
12588            three <s|>
12589            additional edit
12590        "},
12591        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12592        true,
12593        counter.clone(),
12594        &mut cx,
12595    )
12596    .await;
12597    cx.condition(|editor, _| editor.context_menu_visible())
12598        .await;
12599    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12600
12601    cx.simulate_keystroke("i");
12602
12603    handle_completion_request(
12604        indoc! {"
12605            one.second_completion
12606            two si
12607            three <si|>
12608            additional edit
12609        "},
12610        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12611        true,
12612        counter.clone(),
12613        &mut cx,
12614    )
12615    .await;
12616    cx.condition(|editor, _| editor.context_menu_visible())
12617        .await;
12618    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12619
12620    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12621        editor
12622            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12623            .unwrap()
12624    });
12625    cx.assert_editor_state(indoc! {"
12626        one.second_completion
12627        two sixth_completionˇ
12628        three sixth_completionˇ
12629        additional edit
12630    "});
12631
12632    apply_additional_edits.await.unwrap();
12633
12634    update_test_language_settings(&mut cx, |settings| {
12635        settings.defaults.show_completions_on_input = Some(false);
12636    });
12637    cx.set_state("editorˇ");
12638    cx.simulate_keystroke(".");
12639    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12640    cx.simulate_keystrokes("c l o");
12641    cx.assert_editor_state("editor.cloˇ");
12642    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12643    cx.update_editor(|editor, window, cx| {
12644        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12645    });
12646    handle_completion_request(
12647        "editor.<clo|>",
12648        vec!["close", "clobber"],
12649        true,
12650        counter.clone(),
12651        &mut cx,
12652    )
12653    .await;
12654    cx.condition(|editor, _| editor.context_menu_visible())
12655        .await;
12656    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12657
12658    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12659        editor
12660            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12661            .unwrap()
12662    });
12663    cx.assert_editor_state("editor.clobberˇ");
12664    handle_resolve_completion_request(&mut cx, None).await;
12665    apply_additional_edits.await.unwrap();
12666}
12667
12668#[gpui::test]
12669async fn test_completion_reuse(cx: &mut TestAppContext) {
12670    init_test(cx, |_| {});
12671
12672    let mut cx = EditorLspTestContext::new_rust(
12673        lsp::ServerCapabilities {
12674            completion_provider: Some(lsp::CompletionOptions {
12675                trigger_characters: Some(vec![".".to_string()]),
12676                ..Default::default()
12677            }),
12678            ..Default::default()
12679        },
12680        cx,
12681    )
12682    .await;
12683
12684    let counter = Arc::new(AtomicUsize::new(0));
12685    cx.set_state("objˇ");
12686    cx.simulate_keystroke(".");
12687
12688    // Initial completion request returns complete results
12689    let is_incomplete = false;
12690    handle_completion_request(
12691        "obj.|<>",
12692        vec!["a", "ab", "abc"],
12693        is_incomplete,
12694        counter.clone(),
12695        &mut cx,
12696    )
12697    .await;
12698    cx.run_until_parked();
12699    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12700    cx.assert_editor_state("obj.ˇ");
12701    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12702
12703    // Type "a" - filters existing completions
12704    cx.simulate_keystroke("a");
12705    cx.run_until_parked();
12706    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12707    cx.assert_editor_state("obj.aˇ");
12708    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12709
12710    // Type "b" - filters existing completions
12711    cx.simulate_keystroke("b");
12712    cx.run_until_parked();
12713    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12714    cx.assert_editor_state("obj.abˇ");
12715    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12716
12717    // Type "c" - filters existing completions
12718    cx.simulate_keystroke("c");
12719    cx.run_until_parked();
12720    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12721    cx.assert_editor_state("obj.abcˇ");
12722    check_displayed_completions(vec!["abc"], &mut cx);
12723
12724    // Backspace to delete "c" - filters existing completions
12725    cx.update_editor(|editor, window, cx| {
12726        editor.backspace(&Backspace, window, cx);
12727    });
12728    cx.run_until_parked();
12729    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12730    cx.assert_editor_state("obj.abˇ");
12731    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12732
12733    // Moving cursor to the left dismisses menu.
12734    cx.update_editor(|editor, window, cx| {
12735        editor.move_left(&MoveLeft, window, cx);
12736    });
12737    cx.run_until_parked();
12738    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12739    cx.assert_editor_state("obj.aˇb");
12740    cx.update_editor(|editor, _, _| {
12741        assert_eq!(editor.context_menu_visible(), false);
12742    });
12743
12744    // Type "b" - new request
12745    cx.simulate_keystroke("b");
12746    let is_incomplete = false;
12747    handle_completion_request(
12748        "obj.<ab|>a",
12749        vec!["ab", "abc"],
12750        is_incomplete,
12751        counter.clone(),
12752        &mut cx,
12753    )
12754    .await;
12755    cx.run_until_parked();
12756    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12757    cx.assert_editor_state("obj.abˇb");
12758    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12759
12760    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12761    cx.update_editor(|editor, window, cx| {
12762        editor.backspace(&Backspace, window, cx);
12763    });
12764    let is_incomplete = false;
12765    handle_completion_request(
12766        "obj.<a|>b",
12767        vec!["a", "ab", "abc"],
12768        is_incomplete,
12769        counter.clone(),
12770        &mut cx,
12771    )
12772    .await;
12773    cx.run_until_parked();
12774    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12775    cx.assert_editor_state("obj.aˇb");
12776    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12777
12778    // Backspace to delete "a" - dismisses menu.
12779    cx.update_editor(|editor, window, cx| {
12780        editor.backspace(&Backspace, window, cx);
12781    });
12782    cx.run_until_parked();
12783    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12784    cx.assert_editor_state("obj.ˇb");
12785    cx.update_editor(|editor, _, _| {
12786        assert_eq!(editor.context_menu_visible(), false);
12787    });
12788}
12789
12790#[gpui::test]
12791async fn test_word_completion(cx: &mut TestAppContext) {
12792    let lsp_fetch_timeout_ms = 10;
12793    init_test(cx, |language_settings| {
12794        language_settings.defaults.completions = Some(CompletionSettings {
12795            words: WordsCompletionMode::Fallback,
12796            lsp: true,
12797            lsp_fetch_timeout_ms: 10,
12798            lsp_insert_mode: LspInsertMode::Insert,
12799        });
12800    });
12801
12802    let mut cx = EditorLspTestContext::new_rust(
12803        lsp::ServerCapabilities {
12804            completion_provider: Some(lsp::CompletionOptions {
12805                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12806                ..lsp::CompletionOptions::default()
12807            }),
12808            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12809            ..lsp::ServerCapabilities::default()
12810        },
12811        cx,
12812    )
12813    .await;
12814
12815    let throttle_completions = Arc::new(AtomicBool::new(false));
12816
12817    let lsp_throttle_completions = throttle_completions.clone();
12818    let _completion_requests_handler =
12819        cx.lsp
12820            .server
12821            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12822                let lsp_throttle_completions = lsp_throttle_completions.clone();
12823                let cx = cx.clone();
12824                async move {
12825                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12826                        cx.background_executor()
12827                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12828                            .await;
12829                    }
12830                    Ok(Some(lsp::CompletionResponse::Array(vec![
12831                        lsp::CompletionItem {
12832                            label: "first".into(),
12833                            ..lsp::CompletionItem::default()
12834                        },
12835                        lsp::CompletionItem {
12836                            label: "last".into(),
12837                            ..lsp::CompletionItem::default()
12838                        },
12839                    ])))
12840                }
12841            });
12842
12843    cx.set_state(indoc! {"
12844        oneˇ
12845        two
12846        three
12847    "});
12848    cx.simulate_keystroke(".");
12849    cx.executor().run_until_parked();
12850    cx.condition(|editor, _| editor.context_menu_visible())
12851        .await;
12852    cx.update_editor(|editor, window, cx| {
12853        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12854        {
12855            assert_eq!(
12856                completion_menu_entries(&menu),
12857                &["first", "last"],
12858                "When LSP server is fast to reply, no fallback word completions are used"
12859            );
12860        } else {
12861            panic!("expected completion menu to be open");
12862        }
12863        editor.cancel(&Cancel, window, cx);
12864    });
12865    cx.executor().run_until_parked();
12866    cx.condition(|editor, _| !editor.context_menu_visible())
12867        .await;
12868
12869    throttle_completions.store(true, atomic::Ordering::Release);
12870    cx.simulate_keystroke(".");
12871    cx.executor()
12872        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12873    cx.executor().run_until_parked();
12874    cx.condition(|editor, _| editor.context_menu_visible())
12875        .await;
12876    cx.update_editor(|editor, _, _| {
12877        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12878        {
12879            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12880                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12881        } else {
12882            panic!("expected completion menu to be open");
12883        }
12884    });
12885}
12886
12887#[gpui::test]
12888async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12889    init_test(cx, |language_settings| {
12890        language_settings.defaults.completions = Some(CompletionSettings {
12891            words: WordsCompletionMode::Enabled,
12892            lsp: true,
12893            lsp_fetch_timeout_ms: 0,
12894            lsp_insert_mode: LspInsertMode::Insert,
12895        });
12896    });
12897
12898    let mut cx = EditorLspTestContext::new_rust(
12899        lsp::ServerCapabilities {
12900            completion_provider: Some(lsp::CompletionOptions {
12901                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12902                ..lsp::CompletionOptions::default()
12903            }),
12904            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12905            ..lsp::ServerCapabilities::default()
12906        },
12907        cx,
12908    )
12909    .await;
12910
12911    let _completion_requests_handler =
12912        cx.lsp
12913            .server
12914            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12915                Ok(Some(lsp::CompletionResponse::Array(vec![
12916                    lsp::CompletionItem {
12917                        label: "first".into(),
12918                        ..lsp::CompletionItem::default()
12919                    },
12920                    lsp::CompletionItem {
12921                        label: "last".into(),
12922                        ..lsp::CompletionItem::default()
12923                    },
12924                ])))
12925            });
12926
12927    cx.set_state(indoc! {"ˇ
12928        first
12929        last
12930        second
12931    "});
12932    cx.simulate_keystroke(".");
12933    cx.executor().run_until_parked();
12934    cx.condition(|editor, _| editor.context_menu_visible())
12935        .await;
12936    cx.update_editor(|editor, _, _| {
12937        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12938        {
12939            assert_eq!(
12940                completion_menu_entries(&menu),
12941                &["first", "last", "second"],
12942                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12943            );
12944        } else {
12945            panic!("expected completion menu to be open");
12946        }
12947    });
12948}
12949
12950#[gpui::test]
12951async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12952    init_test(cx, |language_settings| {
12953        language_settings.defaults.completions = Some(CompletionSettings {
12954            words: WordsCompletionMode::Disabled,
12955            lsp: true,
12956            lsp_fetch_timeout_ms: 0,
12957            lsp_insert_mode: LspInsertMode::Insert,
12958        });
12959    });
12960
12961    let mut cx = EditorLspTestContext::new_rust(
12962        lsp::ServerCapabilities {
12963            completion_provider: Some(lsp::CompletionOptions {
12964                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12965                ..lsp::CompletionOptions::default()
12966            }),
12967            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12968            ..lsp::ServerCapabilities::default()
12969        },
12970        cx,
12971    )
12972    .await;
12973
12974    let _completion_requests_handler =
12975        cx.lsp
12976            .server
12977            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12978                panic!("LSP completions should not be queried when dealing with word completions")
12979            });
12980
12981    cx.set_state(indoc! {"ˇ
12982        first
12983        last
12984        second
12985    "});
12986    cx.update_editor(|editor, window, cx| {
12987        editor.show_word_completions(&ShowWordCompletions, window, cx);
12988    });
12989    cx.executor().run_until_parked();
12990    cx.condition(|editor, _| editor.context_menu_visible())
12991        .await;
12992    cx.update_editor(|editor, _, _| {
12993        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12994        {
12995            assert_eq!(
12996                completion_menu_entries(&menu),
12997                &["first", "last", "second"],
12998                "`ShowWordCompletions` action should show word completions"
12999            );
13000        } else {
13001            panic!("expected completion menu to be open");
13002        }
13003    });
13004
13005    cx.simulate_keystroke("l");
13006    cx.executor().run_until_parked();
13007    cx.condition(|editor, _| editor.context_menu_visible())
13008        .await;
13009    cx.update_editor(|editor, _, _| {
13010        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13011        {
13012            assert_eq!(
13013                completion_menu_entries(&menu),
13014                &["last"],
13015                "After showing word completions, further editing should filter them and not query the LSP"
13016            );
13017        } else {
13018            panic!("expected completion menu to be open");
13019        }
13020    });
13021}
13022
13023#[gpui::test]
13024async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13025    init_test(cx, |language_settings| {
13026        language_settings.defaults.completions = Some(CompletionSettings {
13027            words: WordsCompletionMode::Fallback,
13028            lsp: false,
13029            lsp_fetch_timeout_ms: 0,
13030            lsp_insert_mode: LspInsertMode::Insert,
13031        });
13032    });
13033
13034    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13035
13036    cx.set_state(indoc! {"ˇ
13037        0_usize
13038        let
13039        33
13040        4.5f32
13041    "});
13042    cx.update_editor(|editor, window, cx| {
13043        editor.show_completions(&ShowCompletions::default(), window, cx);
13044    });
13045    cx.executor().run_until_parked();
13046    cx.condition(|editor, _| editor.context_menu_visible())
13047        .await;
13048    cx.update_editor(|editor, window, cx| {
13049        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13050        {
13051            assert_eq!(
13052                completion_menu_entries(&menu),
13053                &["let"],
13054                "With no digits in the completion query, no digits should be in the word completions"
13055            );
13056        } else {
13057            panic!("expected completion menu to be open");
13058        }
13059        editor.cancel(&Cancel, window, cx);
13060    });
13061
13062    cx.set_state(indoc! {"13063        0_usize
13064        let
13065        3
13066        33.35f32
13067    "});
13068    cx.update_editor(|editor, window, cx| {
13069        editor.show_completions(&ShowCompletions::default(), window, cx);
13070    });
13071    cx.executor().run_until_parked();
13072    cx.condition(|editor, _| editor.context_menu_visible())
13073        .await;
13074    cx.update_editor(|editor, _, _| {
13075        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13076        {
13077            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13078                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13079        } else {
13080            panic!("expected completion menu to be open");
13081        }
13082    });
13083}
13084
13085fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13086    let position = || lsp::Position {
13087        line: params.text_document_position.position.line,
13088        character: params.text_document_position.position.character,
13089    };
13090    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13091        range: lsp::Range {
13092            start: position(),
13093            end: position(),
13094        },
13095        new_text: text.to_string(),
13096    }))
13097}
13098
13099#[gpui::test]
13100async fn test_multiline_completion(cx: &mut TestAppContext) {
13101    init_test(cx, |_| {});
13102
13103    let fs = FakeFs::new(cx.executor());
13104    fs.insert_tree(
13105        path!("/a"),
13106        json!({
13107            "main.ts": "a",
13108        }),
13109    )
13110    .await;
13111
13112    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13113    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13114    let typescript_language = Arc::new(Language::new(
13115        LanguageConfig {
13116            name: "TypeScript".into(),
13117            matcher: LanguageMatcher {
13118                path_suffixes: vec!["ts".to_string()],
13119                ..LanguageMatcher::default()
13120            },
13121            line_comments: vec!["// ".into()],
13122            ..LanguageConfig::default()
13123        },
13124        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13125    ));
13126    language_registry.add(typescript_language.clone());
13127    let mut fake_servers = language_registry.register_fake_lsp(
13128        "TypeScript",
13129        FakeLspAdapter {
13130            capabilities: lsp::ServerCapabilities {
13131                completion_provider: Some(lsp::CompletionOptions {
13132                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13133                    ..lsp::CompletionOptions::default()
13134                }),
13135                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13136                ..lsp::ServerCapabilities::default()
13137            },
13138            // Emulate vtsls label generation
13139            label_for_completion: Some(Box::new(|item, _| {
13140                let text = if let Some(description) = item
13141                    .label_details
13142                    .as_ref()
13143                    .and_then(|label_details| label_details.description.as_ref())
13144                {
13145                    format!("{} {}", item.label, description)
13146                } else if let Some(detail) = &item.detail {
13147                    format!("{} {}", item.label, detail)
13148                } else {
13149                    item.label.clone()
13150                };
13151                let len = text.len();
13152                Some(language::CodeLabel {
13153                    text,
13154                    runs: Vec::new(),
13155                    filter_range: 0..len,
13156                })
13157            })),
13158            ..FakeLspAdapter::default()
13159        },
13160    );
13161    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13162    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13163    let worktree_id = workspace
13164        .update(cx, |workspace, _window, cx| {
13165            workspace.project().update(cx, |project, cx| {
13166                project.worktrees(cx).next().unwrap().read(cx).id()
13167            })
13168        })
13169        .unwrap();
13170    let _buffer = project
13171        .update(cx, |project, cx| {
13172            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13173        })
13174        .await
13175        .unwrap();
13176    let editor = workspace
13177        .update(cx, |workspace, window, cx| {
13178            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13179        })
13180        .unwrap()
13181        .await
13182        .unwrap()
13183        .downcast::<Editor>()
13184        .unwrap();
13185    let fake_server = fake_servers.next().await.unwrap();
13186
13187    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13188    let multiline_label_2 = "a\nb\nc\n";
13189    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13190    let multiline_description = "d\ne\nf\n";
13191    let multiline_detail_2 = "g\nh\ni\n";
13192
13193    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13194        move |params, _| async move {
13195            Ok(Some(lsp::CompletionResponse::Array(vec![
13196                lsp::CompletionItem {
13197                    label: multiline_label.to_string(),
13198                    text_edit: gen_text_edit(&params, "new_text_1"),
13199                    ..lsp::CompletionItem::default()
13200                },
13201                lsp::CompletionItem {
13202                    label: "single line label 1".to_string(),
13203                    detail: Some(multiline_detail.to_string()),
13204                    text_edit: gen_text_edit(&params, "new_text_2"),
13205                    ..lsp::CompletionItem::default()
13206                },
13207                lsp::CompletionItem {
13208                    label: "single line label 2".to_string(),
13209                    label_details: Some(lsp::CompletionItemLabelDetails {
13210                        description: Some(multiline_description.to_string()),
13211                        detail: None,
13212                    }),
13213                    text_edit: gen_text_edit(&params, "new_text_2"),
13214                    ..lsp::CompletionItem::default()
13215                },
13216                lsp::CompletionItem {
13217                    label: multiline_label_2.to_string(),
13218                    detail: Some(multiline_detail_2.to_string()),
13219                    text_edit: gen_text_edit(&params, "new_text_3"),
13220                    ..lsp::CompletionItem::default()
13221                },
13222                lsp::CompletionItem {
13223                    label: "Label with many     spaces and \t but without newlines".to_string(),
13224                    detail: Some(
13225                        "Details with many     spaces and \t but without newlines".to_string(),
13226                    ),
13227                    text_edit: gen_text_edit(&params, "new_text_4"),
13228                    ..lsp::CompletionItem::default()
13229                },
13230            ])))
13231        },
13232    );
13233
13234    editor.update_in(cx, |editor, window, cx| {
13235        cx.focus_self(window);
13236        editor.move_to_end(&MoveToEnd, window, cx);
13237        editor.handle_input(".", window, cx);
13238    });
13239    cx.run_until_parked();
13240    completion_handle.next().await.unwrap();
13241
13242    editor.update(cx, |editor, _| {
13243        assert!(editor.context_menu_visible());
13244        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13245        {
13246            let completion_labels = menu
13247                .completions
13248                .borrow()
13249                .iter()
13250                .map(|c| c.label.text.clone())
13251                .collect::<Vec<_>>();
13252            assert_eq!(
13253                completion_labels,
13254                &[
13255                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13256                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13257                    "single line label 2 d e f ",
13258                    "a b c g h i ",
13259                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13260                ],
13261                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13262            );
13263
13264            for completion in menu
13265                .completions
13266                .borrow()
13267                .iter() {
13268                    assert_eq!(
13269                        completion.label.filter_range,
13270                        0..completion.label.text.len(),
13271                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13272                    );
13273                }
13274        } else {
13275            panic!("expected completion menu to be open");
13276        }
13277    });
13278}
13279
13280#[gpui::test]
13281async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13282    init_test(cx, |_| {});
13283    let mut cx = EditorLspTestContext::new_rust(
13284        lsp::ServerCapabilities {
13285            completion_provider: Some(lsp::CompletionOptions {
13286                trigger_characters: Some(vec![".".to_string()]),
13287                ..Default::default()
13288            }),
13289            ..Default::default()
13290        },
13291        cx,
13292    )
13293    .await;
13294    cx.lsp
13295        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13296            Ok(Some(lsp::CompletionResponse::Array(vec![
13297                lsp::CompletionItem {
13298                    label: "first".into(),
13299                    ..Default::default()
13300                },
13301                lsp::CompletionItem {
13302                    label: "last".into(),
13303                    ..Default::default()
13304                },
13305            ])))
13306        });
13307    cx.set_state("variableˇ");
13308    cx.simulate_keystroke(".");
13309    cx.executor().run_until_parked();
13310
13311    cx.update_editor(|editor, _, _| {
13312        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13313        {
13314            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13315        } else {
13316            panic!("expected completion menu to be open");
13317        }
13318    });
13319
13320    cx.update_editor(|editor, window, cx| {
13321        editor.move_page_down(&MovePageDown::default(), window, cx);
13322        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13323        {
13324            assert!(
13325                menu.selected_item == 1,
13326                "expected PageDown to select the last item from the context menu"
13327            );
13328        } else {
13329            panic!("expected completion menu to stay open after PageDown");
13330        }
13331    });
13332
13333    cx.update_editor(|editor, window, cx| {
13334        editor.move_page_up(&MovePageUp::default(), window, cx);
13335        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13336        {
13337            assert!(
13338                menu.selected_item == 0,
13339                "expected PageUp to select the first item from the context menu"
13340            );
13341        } else {
13342            panic!("expected completion menu to stay open after PageUp");
13343        }
13344    });
13345}
13346
13347#[gpui::test]
13348async fn test_as_is_completions(cx: &mut TestAppContext) {
13349    init_test(cx, |_| {});
13350    let mut cx = EditorLspTestContext::new_rust(
13351        lsp::ServerCapabilities {
13352            completion_provider: Some(lsp::CompletionOptions {
13353                ..Default::default()
13354            }),
13355            ..Default::default()
13356        },
13357        cx,
13358    )
13359    .await;
13360    cx.lsp
13361        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13362            Ok(Some(lsp::CompletionResponse::Array(vec![
13363                lsp::CompletionItem {
13364                    label: "unsafe".into(),
13365                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13366                        range: lsp::Range {
13367                            start: lsp::Position {
13368                                line: 1,
13369                                character: 2,
13370                            },
13371                            end: lsp::Position {
13372                                line: 1,
13373                                character: 3,
13374                            },
13375                        },
13376                        new_text: "unsafe".to_string(),
13377                    })),
13378                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13379                    ..Default::default()
13380                },
13381            ])))
13382        });
13383    cx.set_state("fn a() {}\n");
13384    cx.executor().run_until_parked();
13385    cx.update_editor(|editor, window, cx| {
13386        editor.show_completions(
13387            &ShowCompletions {
13388                trigger: Some("\n".into()),
13389            },
13390            window,
13391            cx,
13392        );
13393    });
13394    cx.executor().run_until_parked();
13395
13396    cx.update_editor(|editor, window, cx| {
13397        editor.confirm_completion(&Default::default(), window, cx)
13398    });
13399    cx.executor().run_until_parked();
13400    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13401}
13402
13403#[gpui::test]
13404async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13405    init_test(cx, |_| {});
13406    let language =
13407        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13408    let mut cx = EditorLspTestContext::new(
13409        language,
13410        lsp::ServerCapabilities {
13411            completion_provider: Some(lsp::CompletionOptions {
13412                ..lsp::CompletionOptions::default()
13413            }),
13414            ..lsp::ServerCapabilities::default()
13415        },
13416        cx,
13417    )
13418    .await;
13419
13420    cx.set_state(
13421        "#ifndef BAR_H
13422#define BAR_H
13423
13424#include <stdbool.h>
13425
13426int fn_branch(bool do_branch1, bool do_branch2);
13427
13428#endif // BAR_H
13429ˇ",
13430    );
13431    cx.executor().run_until_parked();
13432    cx.update_editor(|editor, window, cx| {
13433        editor.handle_input("#", window, cx);
13434    });
13435    cx.executor().run_until_parked();
13436    cx.update_editor(|editor, window, cx| {
13437        editor.handle_input("i", window, cx);
13438    });
13439    cx.executor().run_until_parked();
13440    cx.update_editor(|editor, window, cx| {
13441        editor.handle_input("n", window, cx);
13442    });
13443    cx.executor().run_until_parked();
13444    cx.assert_editor_state(
13445        "#ifndef BAR_H
13446#define BAR_H
13447
13448#include <stdbool.h>
13449
13450int fn_branch(bool do_branch1, bool do_branch2);
13451
13452#endif // BAR_H
13453#inˇ",
13454    );
13455
13456    cx.lsp
13457        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13458            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13459                is_incomplete: false,
13460                item_defaults: None,
13461                items: vec![lsp::CompletionItem {
13462                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13463                    label_details: Some(lsp::CompletionItemLabelDetails {
13464                        detail: Some("header".to_string()),
13465                        description: None,
13466                    }),
13467                    label: " include".to_string(),
13468                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13469                        range: lsp::Range {
13470                            start: lsp::Position {
13471                                line: 8,
13472                                character: 1,
13473                            },
13474                            end: lsp::Position {
13475                                line: 8,
13476                                character: 1,
13477                            },
13478                        },
13479                        new_text: "include \"$0\"".to_string(),
13480                    })),
13481                    sort_text: Some("40b67681include".to_string()),
13482                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13483                    filter_text: Some("include".to_string()),
13484                    insert_text: Some("include \"$0\"".to_string()),
13485                    ..lsp::CompletionItem::default()
13486                }],
13487            })))
13488        });
13489    cx.update_editor(|editor, window, cx| {
13490        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13491    });
13492    cx.executor().run_until_parked();
13493    cx.update_editor(|editor, window, cx| {
13494        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13495    });
13496    cx.executor().run_until_parked();
13497    cx.assert_editor_state(
13498        "#ifndef BAR_H
13499#define BAR_H
13500
13501#include <stdbool.h>
13502
13503int fn_branch(bool do_branch1, bool do_branch2);
13504
13505#endif // BAR_H
13506#include \"ˇ\"",
13507    );
13508
13509    cx.lsp
13510        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13511            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13512                is_incomplete: true,
13513                item_defaults: None,
13514                items: vec![lsp::CompletionItem {
13515                    kind: Some(lsp::CompletionItemKind::FILE),
13516                    label: "AGL/".to_string(),
13517                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13518                        range: lsp::Range {
13519                            start: lsp::Position {
13520                                line: 8,
13521                                character: 10,
13522                            },
13523                            end: lsp::Position {
13524                                line: 8,
13525                                character: 11,
13526                            },
13527                        },
13528                        new_text: "AGL/".to_string(),
13529                    })),
13530                    sort_text: Some("40b67681AGL/".to_string()),
13531                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13532                    filter_text: Some("AGL/".to_string()),
13533                    insert_text: Some("AGL/".to_string()),
13534                    ..lsp::CompletionItem::default()
13535                }],
13536            })))
13537        });
13538    cx.update_editor(|editor, window, cx| {
13539        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13540    });
13541    cx.executor().run_until_parked();
13542    cx.update_editor(|editor, window, cx| {
13543        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13544    });
13545    cx.executor().run_until_parked();
13546    cx.assert_editor_state(
13547        r##"#ifndef BAR_H
13548#define BAR_H
13549
13550#include <stdbool.h>
13551
13552int fn_branch(bool do_branch1, bool do_branch2);
13553
13554#endif // BAR_H
13555#include "AGL/ˇ"##,
13556    );
13557
13558    cx.update_editor(|editor, window, cx| {
13559        editor.handle_input("\"", window, cx);
13560    });
13561    cx.executor().run_until_parked();
13562    cx.assert_editor_state(
13563        r##"#ifndef BAR_H
13564#define BAR_H
13565
13566#include <stdbool.h>
13567
13568int fn_branch(bool do_branch1, bool do_branch2);
13569
13570#endif // BAR_H
13571#include "AGL/"ˇ"##,
13572    );
13573}
13574
13575#[gpui::test]
13576async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13577    init_test(cx, |_| {});
13578
13579    let mut cx = EditorLspTestContext::new_rust(
13580        lsp::ServerCapabilities {
13581            completion_provider: Some(lsp::CompletionOptions {
13582                trigger_characters: Some(vec![".".to_string()]),
13583                resolve_provider: Some(true),
13584                ..Default::default()
13585            }),
13586            ..Default::default()
13587        },
13588        cx,
13589    )
13590    .await;
13591
13592    cx.set_state("fn main() { let a = 2ˇ; }");
13593    cx.simulate_keystroke(".");
13594    let completion_item = lsp::CompletionItem {
13595        label: "Some".into(),
13596        kind: Some(lsp::CompletionItemKind::SNIPPET),
13597        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13598        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13599            kind: lsp::MarkupKind::Markdown,
13600            value: "```rust\nSome(2)\n```".to_string(),
13601        })),
13602        deprecated: Some(false),
13603        sort_text: Some("Some".to_string()),
13604        filter_text: Some("Some".to_string()),
13605        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13606        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13607            range: lsp::Range {
13608                start: lsp::Position {
13609                    line: 0,
13610                    character: 22,
13611                },
13612                end: lsp::Position {
13613                    line: 0,
13614                    character: 22,
13615                },
13616            },
13617            new_text: "Some(2)".to_string(),
13618        })),
13619        additional_text_edits: Some(vec![lsp::TextEdit {
13620            range: lsp::Range {
13621                start: lsp::Position {
13622                    line: 0,
13623                    character: 20,
13624                },
13625                end: lsp::Position {
13626                    line: 0,
13627                    character: 22,
13628                },
13629            },
13630            new_text: "".to_string(),
13631        }]),
13632        ..Default::default()
13633    };
13634
13635    let closure_completion_item = completion_item.clone();
13636    let counter = Arc::new(AtomicUsize::new(0));
13637    let counter_clone = counter.clone();
13638    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13639        let task_completion_item = closure_completion_item.clone();
13640        counter_clone.fetch_add(1, atomic::Ordering::Release);
13641        async move {
13642            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13643                is_incomplete: true,
13644                item_defaults: None,
13645                items: vec![task_completion_item],
13646            })))
13647        }
13648    });
13649
13650    cx.condition(|editor, _| editor.context_menu_visible())
13651        .await;
13652    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13653    assert!(request.next().await.is_some());
13654    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13655
13656    cx.simulate_keystrokes("S o m");
13657    cx.condition(|editor, _| editor.context_menu_visible())
13658        .await;
13659    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13660    assert!(request.next().await.is_some());
13661    assert!(request.next().await.is_some());
13662    assert!(request.next().await.is_some());
13663    request.close();
13664    assert!(request.next().await.is_none());
13665    assert_eq!(
13666        counter.load(atomic::Ordering::Acquire),
13667        4,
13668        "With the completions menu open, only one LSP request should happen per input"
13669    );
13670}
13671
13672#[gpui::test]
13673async fn test_toggle_comment(cx: &mut TestAppContext) {
13674    init_test(cx, |_| {});
13675    let mut cx = EditorTestContext::new(cx).await;
13676    let language = Arc::new(Language::new(
13677        LanguageConfig {
13678            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13679            ..Default::default()
13680        },
13681        Some(tree_sitter_rust::LANGUAGE.into()),
13682    ));
13683    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13684
13685    // If multiple selections intersect a line, the line is only toggled once.
13686    cx.set_state(indoc! {"
13687        fn a() {
13688            «//b();
13689            ˇ»// «c();
13690            //ˇ»  d();
13691        }
13692    "});
13693
13694    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13695
13696    cx.assert_editor_state(indoc! {"
13697        fn a() {
13698            «b();
13699            c();
13700            ˇ» d();
13701        }
13702    "});
13703
13704    // The comment prefix is inserted at the same column for every line in a
13705    // selection.
13706    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13707
13708    cx.assert_editor_state(indoc! {"
13709        fn a() {
13710            // «b();
13711            // c();
13712            ˇ»//  d();
13713        }
13714    "});
13715
13716    // If a selection ends at the beginning of a line, that line is not toggled.
13717    cx.set_selections_state(indoc! {"
13718        fn a() {
13719            // b();
13720            «// c();
13721        ˇ»    //  d();
13722        }
13723    "});
13724
13725    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13726
13727    cx.assert_editor_state(indoc! {"
13728        fn a() {
13729            // b();
13730            «c();
13731        ˇ»    //  d();
13732        }
13733    "});
13734
13735    // If a selection span a single line and is empty, the line is toggled.
13736    cx.set_state(indoc! {"
13737        fn a() {
13738            a();
13739            b();
13740        ˇ
13741        }
13742    "});
13743
13744    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13745
13746    cx.assert_editor_state(indoc! {"
13747        fn a() {
13748            a();
13749            b();
13750        //•ˇ
13751        }
13752    "});
13753
13754    // If a selection span multiple lines, empty lines are not toggled.
13755    cx.set_state(indoc! {"
13756        fn a() {
13757            «a();
13758
13759            c();ˇ»
13760        }
13761    "});
13762
13763    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13764
13765    cx.assert_editor_state(indoc! {"
13766        fn a() {
13767            // «a();
13768
13769            // c();ˇ»
13770        }
13771    "});
13772
13773    // If a selection includes multiple comment prefixes, all lines are uncommented.
13774    cx.set_state(indoc! {"
13775        fn a() {
13776            «// a();
13777            /// b();
13778            //! c();ˇ»
13779        }
13780    "});
13781
13782    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13783
13784    cx.assert_editor_state(indoc! {"
13785        fn a() {
13786            «a();
13787            b();
13788            c();ˇ»
13789        }
13790    "});
13791}
13792
13793#[gpui::test]
13794async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13795    init_test(cx, |_| {});
13796    let mut cx = EditorTestContext::new(cx).await;
13797    let language = Arc::new(Language::new(
13798        LanguageConfig {
13799            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13800            ..Default::default()
13801        },
13802        Some(tree_sitter_rust::LANGUAGE.into()),
13803    ));
13804    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13805
13806    let toggle_comments = &ToggleComments {
13807        advance_downwards: false,
13808        ignore_indent: true,
13809    };
13810
13811    // If multiple selections intersect a line, the line is only toggled once.
13812    cx.set_state(indoc! {"
13813        fn a() {
13814        //    «b();
13815        //    c();
13816        //    ˇ» d();
13817        }
13818    "});
13819
13820    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13821
13822    cx.assert_editor_state(indoc! {"
13823        fn a() {
13824            «b();
13825            c();
13826            ˇ» d();
13827        }
13828    "});
13829
13830    // The comment prefix is inserted at the beginning of each line
13831    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13832
13833    cx.assert_editor_state(indoc! {"
13834        fn a() {
13835        //    «b();
13836        //    c();
13837        //    ˇ» d();
13838        }
13839    "});
13840
13841    // If a selection ends at the beginning of a line, that line is not toggled.
13842    cx.set_selections_state(indoc! {"
13843        fn a() {
13844        //    b();
13845        //    «c();
13846        ˇ»//     d();
13847        }
13848    "});
13849
13850    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13851
13852    cx.assert_editor_state(indoc! {"
13853        fn a() {
13854        //    b();
13855            «c();
13856        ˇ»//     d();
13857        }
13858    "});
13859
13860    // If a selection span a single line and is empty, the line is toggled.
13861    cx.set_state(indoc! {"
13862        fn a() {
13863            a();
13864            b();
13865        ˇ
13866        }
13867    "});
13868
13869    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13870
13871    cx.assert_editor_state(indoc! {"
13872        fn a() {
13873            a();
13874            b();
13875        //ˇ
13876        }
13877    "});
13878
13879    // If a selection span multiple lines, empty lines are not toggled.
13880    cx.set_state(indoc! {"
13881        fn a() {
13882            «a();
13883
13884            c();ˇ»
13885        }
13886    "});
13887
13888    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13889
13890    cx.assert_editor_state(indoc! {"
13891        fn a() {
13892        //    «a();
13893
13894        //    c();ˇ»
13895        }
13896    "});
13897
13898    // If a selection includes multiple comment prefixes, all lines are uncommented.
13899    cx.set_state(indoc! {"
13900        fn a() {
13901        //    «a();
13902        ///    b();
13903        //!    c();ˇ»
13904        }
13905    "});
13906
13907    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13908
13909    cx.assert_editor_state(indoc! {"
13910        fn a() {
13911            «a();
13912            b();
13913            c();ˇ»
13914        }
13915    "});
13916}
13917
13918#[gpui::test]
13919async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13920    init_test(cx, |_| {});
13921
13922    let language = Arc::new(Language::new(
13923        LanguageConfig {
13924            line_comments: vec!["// ".into()],
13925            ..Default::default()
13926        },
13927        Some(tree_sitter_rust::LANGUAGE.into()),
13928    ));
13929
13930    let mut cx = EditorTestContext::new(cx).await;
13931
13932    cx.language_registry().add(language.clone());
13933    cx.update_buffer(|buffer, cx| {
13934        buffer.set_language(Some(language), cx);
13935    });
13936
13937    let toggle_comments = &ToggleComments {
13938        advance_downwards: true,
13939        ignore_indent: false,
13940    };
13941
13942    // Single cursor on one line -> advance
13943    // Cursor moves horizontally 3 characters as well on non-blank line
13944    cx.set_state(indoc!(
13945        "fn a() {
13946             ˇdog();
13947             cat();
13948        }"
13949    ));
13950    cx.update_editor(|editor, window, cx| {
13951        editor.toggle_comments(toggle_comments, window, cx);
13952    });
13953    cx.assert_editor_state(indoc!(
13954        "fn a() {
13955             // dog();
13956             catˇ();
13957        }"
13958    ));
13959
13960    // Single selection on one line -> don't advance
13961    cx.set_state(indoc!(
13962        "fn a() {
13963             «dog()ˇ»;
13964             cat();
13965        }"
13966    ));
13967    cx.update_editor(|editor, window, cx| {
13968        editor.toggle_comments(toggle_comments, window, cx);
13969    });
13970    cx.assert_editor_state(indoc!(
13971        "fn a() {
13972             // «dog()ˇ»;
13973             cat();
13974        }"
13975    ));
13976
13977    // Multiple cursors on one line -> advance
13978    cx.set_state(indoc!(
13979        "fn a() {
13980             ˇdˇog();
13981             cat();
13982        }"
13983    ));
13984    cx.update_editor(|editor, window, cx| {
13985        editor.toggle_comments(toggle_comments, window, cx);
13986    });
13987    cx.assert_editor_state(indoc!(
13988        "fn a() {
13989             // dog();
13990             catˇ(ˇ);
13991        }"
13992    ));
13993
13994    // Multiple cursors on one line, with selection -> don't advance
13995    cx.set_state(indoc!(
13996        "fn a() {
13997             ˇdˇog«()ˇ»;
13998             cat();
13999        }"
14000    ));
14001    cx.update_editor(|editor, window, cx| {
14002        editor.toggle_comments(toggle_comments, window, cx);
14003    });
14004    cx.assert_editor_state(indoc!(
14005        "fn a() {
14006             // ˇdˇog«()ˇ»;
14007             cat();
14008        }"
14009    ));
14010
14011    // Single cursor on one line -> advance
14012    // Cursor moves to column 0 on blank line
14013    cx.set_state(indoc!(
14014        "fn a() {
14015             ˇdog();
14016
14017             cat();
14018        }"
14019    ));
14020    cx.update_editor(|editor, window, cx| {
14021        editor.toggle_comments(toggle_comments, window, cx);
14022    });
14023    cx.assert_editor_state(indoc!(
14024        "fn a() {
14025             // dog();
14026        ˇ
14027             cat();
14028        }"
14029    ));
14030
14031    // Single cursor on one line -> advance
14032    // Cursor starts and ends at column 0
14033    cx.set_state(indoc!(
14034        "fn a() {
14035         ˇ    dog();
14036             cat();
14037        }"
14038    ));
14039    cx.update_editor(|editor, window, cx| {
14040        editor.toggle_comments(toggle_comments, window, cx);
14041    });
14042    cx.assert_editor_state(indoc!(
14043        "fn a() {
14044             // dog();
14045         ˇ    cat();
14046        }"
14047    ));
14048}
14049
14050#[gpui::test]
14051async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14052    init_test(cx, |_| {});
14053
14054    let mut cx = EditorTestContext::new(cx).await;
14055
14056    let html_language = Arc::new(
14057        Language::new(
14058            LanguageConfig {
14059                name: "HTML".into(),
14060                block_comment: Some(BlockCommentConfig {
14061                    start: "<!-- ".into(),
14062                    prefix: "".into(),
14063                    end: " -->".into(),
14064                    tab_size: 0,
14065                }),
14066                ..Default::default()
14067            },
14068            Some(tree_sitter_html::LANGUAGE.into()),
14069        )
14070        .with_injection_query(
14071            r#"
14072            (script_element
14073                (raw_text) @injection.content
14074                (#set! injection.language "javascript"))
14075            "#,
14076        )
14077        .unwrap(),
14078    );
14079
14080    let javascript_language = Arc::new(Language::new(
14081        LanguageConfig {
14082            name: "JavaScript".into(),
14083            line_comments: vec!["// ".into()],
14084            ..Default::default()
14085        },
14086        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14087    ));
14088
14089    cx.language_registry().add(html_language.clone());
14090    cx.language_registry().add(javascript_language.clone());
14091    cx.update_buffer(|buffer, cx| {
14092        buffer.set_language(Some(html_language), cx);
14093    });
14094
14095    // Toggle comments for empty selections
14096    cx.set_state(
14097        &r#"
14098            <p>A</p>ˇ
14099            <p>B</p>ˇ
14100            <p>C</p>ˇ
14101        "#
14102        .unindent(),
14103    );
14104    cx.update_editor(|editor, window, cx| {
14105        editor.toggle_comments(&ToggleComments::default(), window, cx)
14106    });
14107    cx.assert_editor_state(
14108        &r#"
14109            <!-- <p>A</p>ˇ -->
14110            <!-- <p>B</p>ˇ -->
14111            <!-- <p>C</p>ˇ -->
14112        "#
14113        .unindent(),
14114    );
14115    cx.update_editor(|editor, window, cx| {
14116        editor.toggle_comments(&ToggleComments::default(), window, cx)
14117    });
14118    cx.assert_editor_state(
14119        &r#"
14120            <p>A</p>ˇ
14121            <p>B</p>ˇ
14122            <p>C</p>ˇ
14123        "#
14124        .unindent(),
14125    );
14126
14127    // Toggle comments for mixture of empty and non-empty selections, where
14128    // multiple selections occupy a given line.
14129    cx.set_state(
14130        &r#"
14131            <p>A«</p>
14132            <p>ˇ»B</p>ˇ
14133            <p>C«</p>
14134            <p>ˇ»D</p>ˇ
14135        "#
14136        .unindent(),
14137    );
14138
14139    cx.update_editor(|editor, window, cx| {
14140        editor.toggle_comments(&ToggleComments::default(), window, cx)
14141    });
14142    cx.assert_editor_state(
14143        &r#"
14144            <!-- <p>A«</p>
14145            <p>ˇ»B</p>ˇ -->
14146            <!-- <p>C«</p>
14147            <p>ˇ»D</p>ˇ -->
14148        "#
14149        .unindent(),
14150    );
14151    cx.update_editor(|editor, window, cx| {
14152        editor.toggle_comments(&ToggleComments::default(), window, cx)
14153    });
14154    cx.assert_editor_state(
14155        &r#"
14156            <p>A«</p>
14157            <p>ˇ»B</p>ˇ
14158            <p>C«</p>
14159            <p>ˇ»D</p>ˇ
14160        "#
14161        .unindent(),
14162    );
14163
14164    // Toggle comments when different languages are active for different
14165    // selections.
14166    cx.set_state(
14167        &r#"
14168            ˇ<script>
14169                ˇvar x = new Y();
14170            ˇ</script>
14171        "#
14172        .unindent(),
14173    );
14174    cx.executor().run_until_parked();
14175    cx.update_editor(|editor, window, cx| {
14176        editor.toggle_comments(&ToggleComments::default(), window, cx)
14177    });
14178    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14179    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14180    cx.assert_editor_state(
14181        &r#"
14182            <!-- ˇ<script> -->
14183                // ˇvar x = new Y();
14184            <!-- ˇ</script> -->
14185        "#
14186        .unindent(),
14187    );
14188}
14189
14190#[gpui::test]
14191fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14192    init_test(cx, |_| {});
14193
14194    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14195    let multibuffer = cx.new(|cx| {
14196        let mut multibuffer = MultiBuffer::new(ReadWrite);
14197        multibuffer.push_excerpts(
14198            buffer.clone(),
14199            [
14200                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14201                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14202            ],
14203            cx,
14204        );
14205        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14206        multibuffer
14207    });
14208
14209    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14210    editor.update_in(cx, |editor, window, cx| {
14211        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14212        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14213            s.select_ranges([
14214                Point::new(0, 0)..Point::new(0, 0),
14215                Point::new(1, 0)..Point::new(1, 0),
14216            ])
14217        });
14218
14219        editor.handle_input("X", window, cx);
14220        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14221        assert_eq!(
14222            editor.selections.ranges(cx),
14223            [
14224                Point::new(0, 1)..Point::new(0, 1),
14225                Point::new(1, 1)..Point::new(1, 1),
14226            ]
14227        );
14228
14229        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14231            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14232        });
14233        editor.backspace(&Default::default(), window, cx);
14234        assert_eq!(editor.text(cx), "Xa\nbbb");
14235        assert_eq!(
14236            editor.selections.ranges(cx),
14237            [Point::new(1, 0)..Point::new(1, 0)]
14238        );
14239
14240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14241            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14242        });
14243        editor.backspace(&Default::default(), window, cx);
14244        assert_eq!(editor.text(cx), "X\nbb");
14245        assert_eq!(
14246            editor.selections.ranges(cx),
14247            [Point::new(0, 1)..Point::new(0, 1)]
14248        );
14249    });
14250}
14251
14252#[gpui::test]
14253fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14254    init_test(cx, |_| {});
14255
14256    let markers = vec![('[', ']').into(), ('(', ')').into()];
14257    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14258        indoc! {"
14259            [aaaa
14260            (bbbb]
14261            cccc)",
14262        },
14263        markers.clone(),
14264    );
14265    let excerpt_ranges = markers.into_iter().map(|marker| {
14266        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14267        ExcerptRange::new(context.clone())
14268    });
14269    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14270    let multibuffer = cx.new(|cx| {
14271        let mut multibuffer = MultiBuffer::new(ReadWrite);
14272        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14273        multibuffer
14274    });
14275
14276    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14277    editor.update_in(cx, |editor, window, cx| {
14278        let (expected_text, selection_ranges) = marked_text_ranges(
14279            indoc! {"
14280                aaaa
14281                bˇbbb
14282                bˇbbˇb
14283                cccc"
14284            },
14285            true,
14286        );
14287        assert_eq!(editor.text(cx), expected_text);
14288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14289            s.select_ranges(selection_ranges)
14290        });
14291
14292        editor.handle_input("X", window, cx);
14293
14294        let (expected_text, expected_selections) = marked_text_ranges(
14295            indoc! {"
14296                aaaa
14297                bXˇbbXb
14298                bXˇbbXˇb
14299                cccc"
14300            },
14301            false,
14302        );
14303        assert_eq!(editor.text(cx), expected_text);
14304        assert_eq!(editor.selections.ranges(cx), expected_selections);
14305
14306        editor.newline(&Newline, window, cx);
14307        let (expected_text, expected_selections) = marked_text_ranges(
14308            indoc! {"
14309                aaaa
14310                bX
14311                ˇbbX
14312                b
14313                bX
14314                ˇbbX
14315                ˇb
14316                cccc"
14317            },
14318            false,
14319        );
14320        assert_eq!(editor.text(cx), expected_text);
14321        assert_eq!(editor.selections.ranges(cx), expected_selections);
14322    });
14323}
14324
14325#[gpui::test]
14326fn test_refresh_selections(cx: &mut TestAppContext) {
14327    init_test(cx, |_| {});
14328
14329    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14330    let mut excerpt1_id = None;
14331    let multibuffer = cx.new(|cx| {
14332        let mut multibuffer = MultiBuffer::new(ReadWrite);
14333        excerpt1_id = multibuffer
14334            .push_excerpts(
14335                buffer.clone(),
14336                [
14337                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14338                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14339                ],
14340                cx,
14341            )
14342            .into_iter()
14343            .next();
14344        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14345        multibuffer
14346    });
14347
14348    let editor = cx.add_window(|window, cx| {
14349        let mut editor = build_editor(multibuffer.clone(), window, cx);
14350        let snapshot = editor.snapshot(window, cx);
14351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14352            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14353        });
14354        editor.begin_selection(
14355            Point::new(2, 1).to_display_point(&snapshot),
14356            true,
14357            1,
14358            window,
14359            cx,
14360        );
14361        assert_eq!(
14362            editor.selections.ranges(cx),
14363            [
14364                Point::new(1, 3)..Point::new(1, 3),
14365                Point::new(2, 1)..Point::new(2, 1),
14366            ]
14367        );
14368        editor
14369    });
14370
14371    // Refreshing selections is a no-op when excerpts haven't changed.
14372    _ = editor.update(cx, |editor, window, cx| {
14373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14374        assert_eq!(
14375            editor.selections.ranges(cx),
14376            [
14377                Point::new(1, 3)..Point::new(1, 3),
14378                Point::new(2, 1)..Point::new(2, 1),
14379            ]
14380        );
14381    });
14382
14383    multibuffer.update(cx, |multibuffer, cx| {
14384        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14385    });
14386    _ = editor.update(cx, |editor, window, cx| {
14387        // Removing an excerpt causes the first selection to become degenerate.
14388        assert_eq!(
14389            editor.selections.ranges(cx),
14390            [
14391                Point::new(0, 0)..Point::new(0, 0),
14392                Point::new(0, 1)..Point::new(0, 1)
14393            ]
14394        );
14395
14396        // Refreshing selections will relocate the first selection to the original buffer
14397        // location.
14398        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14399        assert_eq!(
14400            editor.selections.ranges(cx),
14401            [
14402                Point::new(0, 1)..Point::new(0, 1),
14403                Point::new(0, 3)..Point::new(0, 3)
14404            ]
14405        );
14406        assert!(editor.selections.pending_anchor().is_some());
14407    });
14408}
14409
14410#[gpui::test]
14411fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14412    init_test(cx, |_| {});
14413
14414    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14415    let mut excerpt1_id = None;
14416    let multibuffer = cx.new(|cx| {
14417        let mut multibuffer = MultiBuffer::new(ReadWrite);
14418        excerpt1_id = multibuffer
14419            .push_excerpts(
14420                buffer.clone(),
14421                [
14422                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14423                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14424                ],
14425                cx,
14426            )
14427            .into_iter()
14428            .next();
14429        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14430        multibuffer
14431    });
14432
14433    let editor = cx.add_window(|window, cx| {
14434        let mut editor = build_editor(multibuffer.clone(), window, cx);
14435        let snapshot = editor.snapshot(window, cx);
14436        editor.begin_selection(
14437            Point::new(1, 3).to_display_point(&snapshot),
14438            false,
14439            1,
14440            window,
14441            cx,
14442        );
14443        assert_eq!(
14444            editor.selections.ranges(cx),
14445            [Point::new(1, 3)..Point::new(1, 3)]
14446        );
14447        editor
14448    });
14449
14450    multibuffer.update(cx, |multibuffer, cx| {
14451        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14452    });
14453    _ = editor.update(cx, |editor, window, cx| {
14454        assert_eq!(
14455            editor.selections.ranges(cx),
14456            [Point::new(0, 0)..Point::new(0, 0)]
14457        );
14458
14459        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14461        assert_eq!(
14462            editor.selections.ranges(cx),
14463            [Point::new(0, 3)..Point::new(0, 3)]
14464        );
14465        assert!(editor.selections.pending_anchor().is_some());
14466    });
14467}
14468
14469#[gpui::test]
14470async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14471    init_test(cx, |_| {});
14472
14473    let language = Arc::new(
14474        Language::new(
14475            LanguageConfig {
14476                brackets: BracketPairConfig {
14477                    pairs: vec![
14478                        BracketPair {
14479                            start: "{".to_string(),
14480                            end: "}".to_string(),
14481                            close: true,
14482                            surround: true,
14483                            newline: true,
14484                        },
14485                        BracketPair {
14486                            start: "/* ".to_string(),
14487                            end: " */".to_string(),
14488                            close: true,
14489                            surround: true,
14490                            newline: true,
14491                        },
14492                    ],
14493                    ..Default::default()
14494                },
14495                ..Default::default()
14496            },
14497            Some(tree_sitter_rust::LANGUAGE.into()),
14498        )
14499        .with_indents_query("")
14500        .unwrap(),
14501    );
14502
14503    let text = concat!(
14504        "{   }\n",     //
14505        "  x\n",       //
14506        "  /*   */\n", //
14507        "x\n",         //
14508        "{{} }\n",     //
14509    );
14510
14511    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14512    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14513    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14514    editor
14515        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14516        .await;
14517
14518    editor.update_in(cx, |editor, window, cx| {
14519        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14520            s.select_display_ranges([
14521                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14522                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14523                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14524            ])
14525        });
14526        editor.newline(&Newline, window, cx);
14527
14528        assert_eq!(
14529            editor.buffer().read(cx).read(cx).text(),
14530            concat!(
14531                "{ \n",    // Suppress rustfmt
14532                "\n",      //
14533                "}\n",     //
14534                "  x\n",   //
14535                "  /* \n", //
14536                "  \n",    //
14537                "  */\n",  //
14538                "x\n",     //
14539                "{{} \n",  //
14540                "}\n",     //
14541            )
14542        );
14543    });
14544}
14545
14546#[gpui::test]
14547fn test_highlighted_ranges(cx: &mut TestAppContext) {
14548    init_test(cx, |_| {});
14549
14550    let editor = cx.add_window(|window, cx| {
14551        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14552        build_editor(buffer.clone(), window, cx)
14553    });
14554
14555    _ = editor.update(cx, |editor, window, cx| {
14556        struct Type1;
14557        struct Type2;
14558
14559        let buffer = editor.buffer.read(cx).snapshot(cx);
14560
14561        let anchor_range =
14562            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14563
14564        editor.highlight_background::<Type1>(
14565            &[
14566                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14567                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14568                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14569                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14570            ],
14571            |_| Hsla::red(),
14572            cx,
14573        );
14574        editor.highlight_background::<Type2>(
14575            &[
14576                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14577                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14578                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14579                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14580            ],
14581            |_| Hsla::green(),
14582            cx,
14583        );
14584
14585        let snapshot = editor.snapshot(window, cx);
14586        let mut highlighted_ranges = editor.background_highlights_in_range(
14587            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14588            &snapshot,
14589            cx.theme(),
14590        );
14591        // Enforce a consistent ordering based on color without relying on the ordering of the
14592        // highlight's `TypeId` which is non-executor.
14593        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14594        assert_eq!(
14595            highlighted_ranges,
14596            &[
14597                (
14598                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14599                    Hsla::red(),
14600                ),
14601                (
14602                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14603                    Hsla::red(),
14604                ),
14605                (
14606                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14607                    Hsla::green(),
14608                ),
14609                (
14610                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14611                    Hsla::green(),
14612                ),
14613            ]
14614        );
14615        assert_eq!(
14616            editor.background_highlights_in_range(
14617                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14618                &snapshot,
14619                cx.theme(),
14620            ),
14621            &[(
14622                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14623                Hsla::red(),
14624            )]
14625        );
14626    });
14627}
14628
14629#[gpui::test]
14630async fn test_following(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
14636    let buffer = project.update(cx, |project, cx| {
14637        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14638        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14639    });
14640    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14641    let follower = cx.update(|cx| {
14642        cx.open_window(
14643            WindowOptions {
14644                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14645                    gpui::Point::new(px(0.), px(0.)),
14646                    gpui::Point::new(px(10.), px(80.)),
14647                ))),
14648                ..Default::default()
14649            },
14650            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14651        )
14652        .unwrap()
14653    });
14654
14655    let is_still_following = Rc::new(RefCell::new(true));
14656    let follower_edit_event_count = Rc::new(RefCell::new(0));
14657    let pending_update = Rc::new(RefCell::new(None));
14658    let leader_entity = leader.root(cx).unwrap();
14659    let follower_entity = follower.root(cx).unwrap();
14660    _ = follower.update(cx, {
14661        let update = pending_update.clone();
14662        let is_still_following = is_still_following.clone();
14663        let follower_edit_event_count = follower_edit_event_count.clone();
14664        |_, window, cx| {
14665            cx.subscribe_in(
14666                &leader_entity,
14667                window,
14668                move |_, leader, event, window, cx| {
14669                    leader.read(cx).add_event_to_update_proto(
14670                        event,
14671                        &mut update.borrow_mut(),
14672                        window,
14673                        cx,
14674                    );
14675                },
14676            )
14677            .detach();
14678
14679            cx.subscribe_in(
14680                &follower_entity,
14681                window,
14682                move |_, _, event: &EditorEvent, _window, _cx| {
14683                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14684                        *is_still_following.borrow_mut() = false;
14685                    }
14686
14687                    if let EditorEvent::BufferEdited = event {
14688                        *follower_edit_event_count.borrow_mut() += 1;
14689                    }
14690                },
14691            )
14692            .detach();
14693        }
14694    });
14695
14696    // Update the selections only
14697    _ = leader.update(cx, |leader, window, cx| {
14698        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14699            s.select_ranges([1..1])
14700        });
14701    });
14702    follower
14703        .update(cx, |follower, window, cx| {
14704            follower.apply_update_proto(
14705                &project,
14706                pending_update.borrow_mut().take().unwrap(),
14707                window,
14708                cx,
14709            )
14710        })
14711        .unwrap()
14712        .await
14713        .unwrap();
14714    _ = follower.update(cx, |follower, _, cx| {
14715        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14716    });
14717    assert!(*is_still_following.borrow());
14718    assert_eq!(*follower_edit_event_count.borrow(), 0);
14719
14720    // Update the scroll position only
14721    _ = leader.update(cx, |leader, window, cx| {
14722        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14723    });
14724    follower
14725        .update(cx, |follower, window, cx| {
14726            follower.apply_update_proto(
14727                &project,
14728                pending_update.borrow_mut().take().unwrap(),
14729                window,
14730                cx,
14731            )
14732        })
14733        .unwrap()
14734        .await
14735        .unwrap();
14736    assert_eq!(
14737        follower
14738            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14739            .unwrap(),
14740        gpui::Point::new(1.5, 3.5)
14741    );
14742    assert!(*is_still_following.borrow());
14743    assert_eq!(*follower_edit_event_count.borrow(), 0);
14744
14745    // Update the selections and scroll position. The follower's scroll position is updated
14746    // via autoscroll, not via the leader's exact scroll position.
14747    _ = leader.update(cx, |leader, window, cx| {
14748        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14749            s.select_ranges([0..0])
14750        });
14751        leader.request_autoscroll(Autoscroll::newest(), cx);
14752        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14753    });
14754    follower
14755        .update(cx, |follower, window, cx| {
14756            follower.apply_update_proto(
14757                &project,
14758                pending_update.borrow_mut().take().unwrap(),
14759                window,
14760                cx,
14761            )
14762        })
14763        .unwrap()
14764        .await
14765        .unwrap();
14766    _ = follower.update(cx, |follower, _, cx| {
14767        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14768        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14769    });
14770    assert!(*is_still_following.borrow());
14771
14772    // Creating a pending selection that precedes another selection
14773    _ = leader.update(cx, |leader, window, cx| {
14774        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14775            s.select_ranges([1..1])
14776        });
14777        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14778    });
14779    follower
14780        .update(cx, |follower, window, cx| {
14781            follower.apply_update_proto(
14782                &project,
14783                pending_update.borrow_mut().take().unwrap(),
14784                window,
14785                cx,
14786            )
14787        })
14788        .unwrap()
14789        .await
14790        .unwrap();
14791    _ = follower.update(cx, |follower, _, cx| {
14792        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14793    });
14794    assert!(*is_still_following.borrow());
14795
14796    // Extend the pending selection so that it surrounds another selection
14797    _ = leader.update(cx, |leader, window, cx| {
14798        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14799    });
14800    follower
14801        .update(cx, |follower, window, cx| {
14802            follower.apply_update_proto(
14803                &project,
14804                pending_update.borrow_mut().take().unwrap(),
14805                window,
14806                cx,
14807            )
14808        })
14809        .unwrap()
14810        .await
14811        .unwrap();
14812    _ = follower.update(cx, |follower, _, cx| {
14813        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14814    });
14815
14816    // Scrolling locally breaks the follow
14817    _ = follower.update(cx, |follower, window, cx| {
14818        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14819        follower.set_scroll_anchor(
14820            ScrollAnchor {
14821                anchor: top_anchor,
14822                offset: gpui::Point::new(0.0, 0.5),
14823            },
14824            window,
14825            cx,
14826        );
14827    });
14828    assert!(!(*is_still_following.borrow()));
14829}
14830
14831#[gpui::test]
14832async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14833    init_test(cx, |_| {});
14834
14835    let fs = FakeFs::new(cx.executor());
14836    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14837    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14838    let pane = workspace
14839        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14840        .unwrap();
14841
14842    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14843
14844    let leader = pane.update_in(cx, |_, window, cx| {
14845        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14846        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14847    });
14848
14849    // Start following the editor when it has no excerpts.
14850    let mut state_message =
14851        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14852    let workspace_entity = workspace.root(cx).unwrap();
14853    let follower_1 = cx
14854        .update_window(*workspace.deref(), |_, window, cx| {
14855            Editor::from_state_proto(
14856                workspace_entity,
14857                ViewId {
14858                    creator: CollaboratorId::PeerId(PeerId::default()),
14859                    id: 0,
14860                },
14861                &mut state_message,
14862                window,
14863                cx,
14864            )
14865        })
14866        .unwrap()
14867        .unwrap()
14868        .await
14869        .unwrap();
14870
14871    let update_message = Rc::new(RefCell::new(None));
14872    follower_1.update_in(cx, {
14873        let update = update_message.clone();
14874        |_, window, cx| {
14875            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14876                leader.read(cx).add_event_to_update_proto(
14877                    event,
14878                    &mut update.borrow_mut(),
14879                    window,
14880                    cx,
14881                );
14882            })
14883            .detach();
14884        }
14885    });
14886
14887    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14888        (
14889            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14890            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14891        )
14892    });
14893
14894    // Insert some excerpts.
14895    leader.update(cx, |leader, cx| {
14896        leader.buffer.update(cx, |multibuffer, cx| {
14897            multibuffer.set_excerpts_for_path(
14898                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14899                buffer_1.clone(),
14900                vec![
14901                    Point::row_range(0..3),
14902                    Point::row_range(1..6),
14903                    Point::row_range(12..15),
14904                ],
14905                0,
14906                cx,
14907            );
14908            multibuffer.set_excerpts_for_path(
14909                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14910                buffer_2.clone(),
14911                vec![Point::row_range(0..6), Point::row_range(8..12)],
14912                0,
14913                cx,
14914            );
14915        });
14916    });
14917
14918    // Apply the update of adding the excerpts.
14919    follower_1
14920        .update_in(cx, |follower, window, cx| {
14921            follower.apply_update_proto(
14922                &project,
14923                update_message.borrow().clone().unwrap(),
14924                window,
14925                cx,
14926            )
14927        })
14928        .await
14929        .unwrap();
14930    assert_eq!(
14931        follower_1.update(cx, |editor, cx| editor.text(cx)),
14932        leader.update(cx, |editor, cx| editor.text(cx))
14933    );
14934    update_message.borrow_mut().take();
14935
14936    // Start following separately after it already has excerpts.
14937    let mut state_message =
14938        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14939    let workspace_entity = workspace.root(cx).unwrap();
14940    let follower_2 = cx
14941        .update_window(*workspace.deref(), |_, window, cx| {
14942            Editor::from_state_proto(
14943                workspace_entity,
14944                ViewId {
14945                    creator: CollaboratorId::PeerId(PeerId::default()),
14946                    id: 0,
14947                },
14948                &mut state_message,
14949                window,
14950                cx,
14951            )
14952        })
14953        .unwrap()
14954        .unwrap()
14955        .await
14956        .unwrap();
14957    assert_eq!(
14958        follower_2.update(cx, |editor, cx| editor.text(cx)),
14959        leader.update(cx, |editor, cx| editor.text(cx))
14960    );
14961
14962    // Remove some excerpts.
14963    leader.update(cx, |leader, cx| {
14964        leader.buffer.update(cx, |multibuffer, cx| {
14965            let excerpt_ids = multibuffer.excerpt_ids();
14966            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14967            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14968        });
14969    });
14970
14971    // Apply the update of removing the excerpts.
14972    follower_1
14973        .update_in(cx, |follower, window, cx| {
14974            follower.apply_update_proto(
14975                &project,
14976                update_message.borrow().clone().unwrap(),
14977                window,
14978                cx,
14979            )
14980        })
14981        .await
14982        .unwrap();
14983    follower_2
14984        .update_in(cx, |follower, window, cx| {
14985            follower.apply_update_proto(
14986                &project,
14987                update_message.borrow().clone().unwrap(),
14988                window,
14989                cx,
14990            )
14991        })
14992        .await
14993        .unwrap();
14994    update_message.borrow_mut().take();
14995    assert_eq!(
14996        follower_1.update(cx, |editor, cx| editor.text(cx)),
14997        leader.update(cx, |editor, cx| editor.text(cx))
14998    );
14999}
15000
15001#[gpui::test]
15002async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15003    init_test(cx, |_| {});
15004
15005    let mut cx = EditorTestContext::new(cx).await;
15006    let lsp_store =
15007        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
15008
15009    cx.set_state(indoc! {"
15010        ˇfn func(abc def: i32) -> u32 {
15011        }
15012    "});
15013
15014    cx.update(|_, cx| {
15015        lsp_store.update(cx, |lsp_store, cx| {
15016            lsp_store
15017                .update_diagnostics(
15018                    LanguageServerId(0),
15019                    lsp::PublishDiagnosticsParams {
15020                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15021                        version: None,
15022                        diagnostics: vec![
15023                            lsp::Diagnostic {
15024                                range: lsp::Range::new(
15025                                    lsp::Position::new(0, 11),
15026                                    lsp::Position::new(0, 12),
15027                                ),
15028                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15029                                ..Default::default()
15030                            },
15031                            lsp::Diagnostic {
15032                                range: lsp::Range::new(
15033                                    lsp::Position::new(0, 12),
15034                                    lsp::Position::new(0, 15),
15035                                ),
15036                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15037                                ..Default::default()
15038                            },
15039                            lsp::Diagnostic {
15040                                range: lsp::Range::new(
15041                                    lsp::Position::new(0, 25),
15042                                    lsp::Position::new(0, 28),
15043                                ),
15044                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15045                                ..Default::default()
15046                            },
15047                        ],
15048                    },
15049                    None,
15050                    DiagnosticSourceKind::Pushed,
15051                    &[],
15052                    cx,
15053                )
15054                .unwrap()
15055        });
15056    });
15057
15058    executor.run_until_parked();
15059
15060    cx.update_editor(|editor, window, cx| {
15061        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15062    });
15063
15064    cx.assert_editor_state(indoc! {"
15065        fn func(abc def: i32) -> ˇu32 {
15066        }
15067    "});
15068
15069    cx.update_editor(|editor, window, cx| {
15070        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15071    });
15072
15073    cx.assert_editor_state(indoc! {"
15074        fn func(abc ˇdef: i32) -> u32 {
15075        }
15076    "});
15077
15078    cx.update_editor(|editor, window, cx| {
15079        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15080    });
15081
15082    cx.assert_editor_state(indoc! {"
15083        fn func(abcˇ def: i32) -> u32 {
15084        }
15085    "});
15086
15087    cx.update_editor(|editor, window, cx| {
15088        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15089    });
15090
15091    cx.assert_editor_state(indoc! {"
15092        fn func(abc def: i32) -> ˇu32 {
15093        }
15094    "});
15095}
15096
15097#[gpui::test]
15098async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15099    init_test(cx, |_| {});
15100
15101    let mut cx = EditorTestContext::new(cx).await;
15102
15103    let diff_base = r#"
15104        use some::mod;
15105
15106        const A: u32 = 42;
15107
15108        fn main() {
15109            println!("hello");
15110
15111            println!("world");
15112        }
15113        "#
15114    .unindent();
15115
15116    // Edits are modified, removed, modified, added
15117    cx.set_state(
15118        &r#"
15119        use some::modified;
15120
15121        ˇ
15122        fn main() {
15123            println!("hello there");
15124
15125            println!("around the");
15126            println!("world");
15127        }
15128        "#
15129        .unindent(),
15130    );
15131
15132    cx.set_head_text(&diff_base);
15133    executor.run_until_parked();
15134
15135    cx.update_editor(|editor, window, cx| {
15136        //Wrap around the bottom of the buffer
15137        for _ in 0..3 {
15138            editor.go_to_next_hunk(&GoToHunk, window, cx);
15139        }
15140    });
15141
15142    cx.assert_editor_state(
15143        &r#"
15144        ˇuse some::modified;
15145
15146
15147        fn main() {
15148            println!("hello there");
15149
15150            println!("around the");
15151            println!("world");
15152        }
15153        "#
15154        .unindent(),
15155    );
15156
15157    cx.update_editor(|editor, window, cx| {
15158        //Wrap around the top of the buffer
15159        for _ in 0..2 {
15160            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15161        }
15162    });
15163
15164    cx.assert_editor_state(
15165        &r#"
15166        use some::modified;
15167
15168
15169        fn main() {
15170        ˇ    println!("hello there");
15171
15172            println!("around the");
15173            println!("world");
15174        }
15175        "#
15176        .unindent(),
15177    );
15178
15179    cx.update_editor(|editor, window, cx| {
15180        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15181    });
15182
15183    cx.assert_editor_state(
15184        &r#"
15185        use some::modified;
15186
15187        ˇ
15188        fn main() {
15189            println!("hello there");
15190
15191            println!("around the");
15192            println!("world");
15193        }
15194        "#
15195        .unindent(),
15196    );
15197
15198    cx.update_editor(|editor, window, cx| {
15199        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15200    });
15201
15202    cx.assert_editor_state(
15203        &r#"
15204        ˇuse some::modified;
15205
15206
15207        fn main() {
15208            println!("hello there");
15209
15210            println!("around the");
15211            println!("world");
15212        }
15213        "#
15214        .unindent(),
15215    );
15216
15217    cx.update_editor(|editor, window, cx| {
15218        for _ in 0..2 {
15219            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15220        }
15221    });
15222
15223    cx.assert_editor_state(
15224        &r#"
15225        use some::modified;
15226
15227
15228        fn main() {
15229        ˇ    println!("hello there");
15230
15231            println!("around the");
15232            println!("world");
15233        }
15234        "#
15235        .unindent(),
15236    );
15237
15238    cx.update_editor(|editor, window, cx| {
15239        editor.fold(&Fold, window, cx);
15240    });
15241
15242    cx.update_editor(|editor, window, cx| {
15243        editor.go_to_next_hunk(&GoToHunk, window, cx);
15244    });
15245
15246    cx.assert_editor_state(
15247        &r#"
15248        ˇuse some::modified;
15249
15250
15251        fn main() {
15252            println!("hello there");
15253
15254            println!("around the");
15255            println!("world");
15256        }
15257        "#
15258        .unindent(),
15259    );
15260}
15261
15262#[test]
15263fn test_split_words() {
15264    fn split(text: &str) -> Vec<&str> {
15265        split_words(text).collect()
15266    }
15267
15268    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15269    assert_eq!(split("hello_world"), &["hello_", "world"]);
15270    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15271    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15272    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15273    assert_eq!(split("helloworld"), &["helloworld"]);
15274
15275    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15276}
15277
15278#[gpui::test]
15279async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15280    init_test(cx, |_| {});
15281
15282    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15283    let mut assert = |before, after| {
15284        let _state_context = cx.set_state(before);
15285        cx.run_until_parked();
15286        cx.update_editor(|editor, window, cx| {
15287            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15288        });
15289        cx.run_until_parked();
15290        cx.assert_editor_state(after);
15291    };
15292
15293    // Outside bracket jumps to outside of matching bracket
15294    assert("console.logˇ(var);", "console.log(var)ˇ;");
15295    assert("console.log(var)ˇ;", "console.logˇ(var);");
15296
15297    // Inside bracket jumps to inside of matching bracket
15298    assert("console.log(ˇvar);", "console.log(varˇ);");
15299    assert("console.log(varˇ);", "console.log(ˇvar);");
15300
15301    // When outside a bracket and inside, favor jumping to the inside bracket
15302    assert(
15303        "console.log('foo', [1, 2, 3]ˇ);",
15304        "console.log(ˇ'foo', [1, 2, 3]);",
15305    );
15306    assert(
15307        "console.log(ˇ'foo', [1, 2, 3]);",
15308        "console.log('foo', [1, 2, 3]ˇ);",
15309    );
15310
15311    // Bias forward if two options are equally likely
15312    assert(
15313        "let result = curried_fun()ˇ();",
15314        "let result = curried_fun()()ˇ;",
15315    );
15316
15317    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15318    assert(
15319        indoc! {"
15320            function test() {
15321                console.log('test')ˇ
15322            }"},
15323        indoc! {"
15324            function test() {
15325                console.logˇ('test')
15326            }"},
15327    );
15328}
15329
15330#[gpui::test]
15331async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15332    init_test(cx, |_| {});
15333
15334    let fs = FakeFs::new(cx.executor());
15335    fs.insert_tree(
15336        path!("/a"),
15337        json!({
15338            "main.rs": "fn main() { let a = 5; }",
15339            "other.rs": "// Test file",
15340        }),
15341    )
15342    .await;
15343    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15344
15345    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15346    language_registry.add(Arc::new(Language::new(
15347        LanguageConfig {
15348            name: "Rust".into(),
15349            matcher: LanguageMatcher {
15350                path_suffixes: vec!["rs".to_string()],
15351                ..Default::default()
15352            },
15353            brackets: BracketPairConfig {
15354                pairs: vec![BracketPair {
15355                    start: "{".to_string(),
15356                    end: "}".to_string(),
15357                    close: true,
15358                    surround: true,
15359                    newline: true,
15360                }],
15361                disabled_scopes_by_bracket_ix: Vec::new(),
15362            },
15363            ..Default::default()
15364        },
15365        Some(tree_sitter_rust::LANGUAGE.into()),
15366    )));
15367    let mut fake_servers = language_registry.register_fake_lsp(
15368        "Rust",
15369        FakeLspAdapter {
15370            capabilities: lsp::ServerCapabilities {
15371                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15372                    first_trigger_character: "{".to_string(),
15373                    more_trigger_character: None,
15374                }),
15375                ..Default::default()
15376            },
15377            ..Default::default()
15378        },
15379    );
15380
15381    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15382
15383    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15384
15385    let worktree_id = workspace
15386        .update(cx, |workspace, _, cx| {
15387            workspace.project().update(cx, |project, cx| {
15388                project.worktrees(cx).next().unwrap().read(cx).id()
15389            })
15390        })
15391        .unwrap();
15392
15393    let buffer = project
15394        .update(cx, |project, cx| {
15395            project.open_local_buffer(path!("/a/main.rs"), cx)
15396        })
15397        .await
15398        .unwrap();
15399    let editor_handle = workspace
15400        .update(cx, |workspace, window, cx| {
15401            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15402        })
15403        .unwrap()
15404        .await
15405        .unwrap()
15406        .downcast::<Editor>()
15407        .unwrap();
15408
15409    cx.executor().start_waiting();
15410    let fake_server = fake_servers.next().await.unwrap();
15411
15412    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15413        |params, _| async move {
15414            assert_eq!(
15415                params.text_document_position.text_document.uri,
15416                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15417            );
15418            assert_eq!(
15419                params.text_document_position.position,
15420                lsp::Position::new(0, 21),
15421            );
15422
15423            Ok(Some(vec![lsp::TextEdit {
15424                new_text: "]".to_string(),
15425                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15426            }]))
15427        },
15428    );
15429
15430    editor_handle.update_in(cx, |editor, window, cx| {
15431        window.focus(&editor.focus_handle(cx));
15432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15433            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15434        });
15435        editor.handle_input("{", window, cx);
15436    });
15437
15438    cx.executor().run_until_parked();
15439
15440    buffer.update(cx, |buffer, _| {
15441        assert_eq!(
15442            buffer.text(),
15443            "fn main() { let a = {5}; }",
15444            "No extra braces from on type formatting should appear in the buffer"
15445        )
15446    });
15447}
15448
15449#[gpui::test(iterations = 20, seeds(31))]
15450async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15451    init_test(cx, |_| {});
15452
15453    let mut cx = EditorLspTestContext::new_rust(
15454        lsp::ServerCapabilities {
15455            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15456                first_trigger_character: ".".to_string(),
15457                more_trigger_character: None,
15458            }),
15459            ..Default::default()
15460        },
15461        cx,
15462    )
15463    .await;
15464
15465    cx.update_buffer(|buffer, _| {
15466        // This causes autoindent to be async.
15467        buffer.set_sync_parse_timeout(Duration::ZERO)
15468    });
15469
15470    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15471    cx.simulate_keystroke("\n");
15472    cx.run_until_parked();
15473
15474    let buffer_cloned =
15475        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15476    let mut request =
15477        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15478            let buffer_cloned = buffer_cloned.clone();
15479            async move {
15480                buffer_cloned.update(&mut cx, |buffer, _| {
15481                    assert_eq!(
15482                        buffer.text(),
15483                        "fn c() {\n    d()\n        .\n}\n",
15484                        "OnTypeFormatting should triggered after autoindent applied"
15485                    )
15486                })?;
15487
15488                Ok(Some(vec![]))
15489            }
15490        });
15491
15492    cx.simulate_keystroke(".");
15493    cx.run_until_parked();
15494
15495    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15496    assert!(request.next().await.is_some());
15497    request.close();
15498    assert!(request.next().await.is_none());
15499}
15500
15501#[gpui::test]
15502async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15503    init_test(cx, |_| {});
15504
15505    let fs = FakeFs::new(cx.executor());
15506    fs.insert_tree(
15507        path!("/a"),
15508        json!({
15509            "main.rs": "fn main() { let a = 5; }",
15510            "other.rs": "// Test file",
15511        }),
15512    )
15513    .await;
15514
15515    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15516
15517    let server_restarts = Arc::new(AtomicUsize::new(0));
15518    let closure_restarts = Arc::clone(&server_restarts);
15519    let language_server_name = "test language server";
15520    let language_name: LanguageName = "Rust".into();
15521
15522    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15523    language_registry.add(Arc::new(Language::new(
15524        LanguageConfig {
15525            name: language_name.clone(),
15526            matcher: LanguageMatcher {
15527                path_suffixes: vec!["rs".to_string()],
15528                ..Default::default()
15529            },
15530            ..Default::default()
15531        },
15532        Some(tree_sitter_rust::LANGUAGE.into()),
15533    )));
15534    let mut fake_servers = language_registry.register_fake_lsp(
15535        "Rust",
15536        FakeLspAdapter {
15537            name: language_server_name,
15538            initialization_options: Some(json!({
15539                "testOptionValue": true
15540            })),
15541            initializer: Some(Box::new(move |fake_server| {
15542                let task_restarts = Arc::clone(&closure_restarts);
15543                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15544                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15545                    futures::future::ready(Ok(()))
15546                });
15547            })),
15548            ..Default::default()
15549        },
15550    );
15551
15552    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15553    let _buffer = project
15554        .update(cx, |project, cx| {
15555            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15556        })
15557        .await
15558        .unwrap();
15559    let _fake_server = fake_servers.next().await.unwrap();
15560    update_test_language_settings(cx, |language_settings| {
15561        language_settings.languages.0.insert(
15562            language_name.clone(),
15563            LanguageSettingsContent {
15564                tab_size: NonZeroU32::new(8),
15565                ..Default::default()
15566            },
15567        );
15568    });
15569    cx.executor().run_until_parked();
15570    assert_eq!(
15571        server_restarts.load(atomic::Ordering::Acquire),
15572        0,
15573        "Should not restart LSP server on an unrelated change"
15574    );
15575
15576    update_test_project_settings(cx, |project_settings| {
15577        project_settings.lsp.insert(
15578            "Some other server name".into(),
15579            LspSettings {
15580                binary: None,
15581                settings: None,
15582                initialization_options: Some(json!({
15583                    "some other init value": false
15584                })),
15585                enable_lsp_tasks: false,
15586            },
15587        );
15588    });
15589    cx.executor().run_until_parked();
15590    assert_eq!(
15591        server_restarts.load(atomic::Ordering::Acquire),
15592        0,
15593        "Should not restart LSP server on an unrelated LSP settings change"
15594    );
15595
15596    update_test_project_settings(cx, |project_settings| {
15597        project_settings.lsp.insert(
15598            language_server_name.into(),
15599            LspSettings {
15600                binary: None,
15601                settings: None,
15602                initialization_options: Some(json!({
15603                    "anotherInitValue": false
15604                })),
15605                enable_lsp_tasks: false,
15606            },
15607        );
15608    });
15609    cx.executor().run_until_parked();
15610    assert_eq!(
15611        server_restarts.load(atomic::Ordering::Acquire),
15612        1,
15613        "Should restart LSP server on a related LSP settings change"
15614    );
15615
15616    update_test_project_settings(cx, |project_settings| {
15617        project_settings.lsp.insert(
15618            language_server_name.into(),
15619            LspSettings {
15620                binary: None,
15621                settings: None,
15622                initialization_options: Some(json!({
15623                    "anotherInitValue": false
15624                })),
15625                enable_lsp_tasks: false,
15626            },
15627        );
15628    });
15629    cx.executor().run_until_parked();
15630    assert_eq!(
15631        server_restarts.load(atomic::Ordering::Acquire),
15632        1,
15633        "Should not restart LSP server on a related LSP settings change that is the same"
15634    );
15635
15636    update_test_project_settings(cx, |project_settings| {
15637        project_settings.lsp.insert(
15638            language_server_name.into(),
15639            LspSettings {
15640                binary: None,
15641                settings: None,
15642                initialization_options: None,
15643                enable_lsp_tasks: false,
15644            },
15645        );
15646    });
15647    cx.executor().run_until_parked();
15648    assert_eq!(
15649        server_restarts.load(atomic::Ordering::Acquire),
15650        2,
15651        "Should restart LSP server on another related LSP settings change"
15652    );
15653}
15654
15655#[gpui::test]
15656async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15657    init_test(cx, |_| {});
15658
15659    let mut cx = EditorLspTestContext::new_rust(
15660        lsp::ServerCapabilities {
15661            completion_provider: Some(lsp::CompletionOptions {
15662                trigger_characters: Some(vec![".".to_string()]),
15663                resolve_provider: Some(true),
15664                ..Default::default()
15665            }),
15666            ..Default::default()
15667        },
15668        cx,
15669    )
15670    .await;
15671
15672    cx.set_state("fn main() { let a = 2ˇ; }");
15673    cx.simulate_keystroke(".");
15674    let completion_item = lsp::CompletionItem {
15675        label: "some".into(),
15676        kind: Some(lsp::CompletionItemKind::SNIPPET),
15677        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15678        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15679            kind: lsp::MarkupKind::Markdown,
15680            value: "```rust\nSome(2)\n```".to_string(),
15681        })),
15682        deprecated: Some(false),
15683        sort_text: Some("fffffff2".to_string()),
15684        filter_text: Some("some".to_string()),
15685        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15686        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15687            range: lsp::Range {
15688                start: lsp::Position {
15689                    line: 0,
15690                    character: 22,
15691                },
15692                end: lsp::Position {
15693                    line: 0,
15694                    character: 22,
15695                },
15696            },
15697            new_text: "Some(2)".to_string(),
15698        })),
15699        additional_text_edits: Some(vec![lsp::TextEdit {
15700            range: lsp::Range {
15701                start: lsp::Position {
15702                    line: 0,
15703                    character: 20,
15704                },
15705                end: lsp::Position {
15706                    line: 0,
15707                    character: 22,
15708                },
15709            },
15710            new_text: "".to_string(),
15711        }]),
15712        ..Default::default()
15713    };
15714
15715    let closure_completion_item = completion_item.clone();
15716    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15717        let task_completion_item = closure_completion_item.clone();
15718        async move {
15719            Ok(Some(lsp::CompletionResponse::Array(vec![
15720                task_completion_item,
15721            ])))
15722        }
15723    });
15724
15725    request.next().await;
15726
15727    cx.condition(|editor, _| editor.context_menu_visible())
15728        .await;
15729    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15730        editor
15731            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15732            .unwrap()
15733    });
15734    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15735
15736    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15737        let task_completion_item = completion_item.clone();
15738        async move { Ok(task_completion_item) }
15739    })
15740    .next()
15741    .await
15742    .unwrap();
15743    apply_additional_edits.await.unwrap();
15744    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15745}
15746
15747#[gpui::test]
15748async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15749    init_test(cx, |_| {});
15750
15751    let mut cx = EditorLspTestContext::new_rust(
15752        lsp::ServerCapabilities {
15753            completion_provider: Some(lsp::CompletionOptions {
15754                trigger_characters: Some(vec![".".to_string()]),
15755                resolve_provider: Some(true),
15756                ..Default::default()
15757            }),
15758            ..Default::default()
15759        },
15760        cx,
15761    )
15762    .await;
15763
15764    cx.set_state("fn main() { let a = 2ˇ; }");
15765    cx.simulate_keystroke(".");
15766
15767    let item1 = lsp::CompletionItem {
15768        label: "method id()".to_string(),
15769        filter_text: Some("id".to_string()),
15770        detail: None,
15771        documentation: None,
15772        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15773            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15774            new_text: ".id".to_string(),
15775        })),
15776        ..lsp::CompletionItem::default()
15777    };
15778
15779    let item2 = lsp::CompletionItem {
15780        label: "other".to_string(),
15781        filter_text: Some("other".to_string()),
15782        detail: None,
15783        documentation: None,
15784        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15785            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15786            new_text: ".other".to_string(),
15787        })),
15788        ..lsp::CompletionItem::default()
15789    };
15790
15791    let item1 = item1.clone();
15792    cx.set_request_handler::<lsp::request::Completion, _, _>({
15793        let item1 = item1.clone();
15794        move |_, _, _| {
15795            let item1 = item1.clone();
15796            let item2 = item2.clone();
15797            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15798        }
15799    })
15800    .next()
15801    .await;
15802
15803    cx.condition(|editor, _| editor.context_menu_visible())
15804        .await;
15805    cx.update_editor(|editor, _, _| {
15806        let context_menu = editor.context_menu.borrow_mut();
15807        let context_menu = context_menu
15808            .as_ref()
15809            .expect("Should have the context menu deployed");
15810        match context_menu {
15811            CodeContextMenu::Completions(completions_menu) => {
15812                let completions = completions_menu.completions.borrow_mut();
15813                assert_eq!(
15814                    completions
15815                        .iter()
15816                        .map(|completion| &completion.label.text)
15817                        .collect::<Vec<_>>(),
15818                    vec!["method id()", "other"]
15819                )
15820            }
15821            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15822        }
15823    });
15824
15825    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15826        let item1 = item1.clone();
15827        move |_, item_to_resolve, _| {
15828            let item1 = item1.clone();
15829            async move {
15830                if item1 == item_to_resolve {
15831                    Ok(lsp::CompletionItem {
15832                        label: "method id()".to_string(),
15833                        filter_text: Some("id".to_string()),
15834                        detail: Some("Now resolved!".to_string()),
15835                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15836                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15837                            range: lsp::Range::new(
15838                                lsp::Position::new(0, 22),
15839                                lsp::Position::new(0, 22),
15840                            ),
15841                            new_text: ".id".to_string(),
15842                        })),
15843                        ..lsp::CompletionItem::default()
15844                    })
15845                } else {
15846                    Ok(item_to_resolve)
15847                }
15848            }
15849        }
15850    })
15851    .next()
15852    .await
15853    .unwrap();
15854    cx.run_until_parked();
15855
15856    cx.update_editor(|editor, window, cx| {
15857        editor.context_menu_next(&Default::default(), window, cx);
15858    });
15859
15860    cx.update_editor(|editor, _, _| {
15861        let context_menu = editor.context_menu.borrow_mut();
15862        let context_menu = context_menu
15863            .as_ref()
15864            .expect("Should have the context menu deployed");
15865        match context_menu {
15866            CodeContextMenu::Completions(completions_menu) => {
15867                let completions = completions_menu.completions.borrow_mut();
15868                assert_eq!(
15869                    completions
15870                        .iter()
15871                        .map(|completion| &completion.label.text)
15872                        .collect::<Vec<_>>(),
15873                    vec!["method id() Now resolved!", "other"],
15874                    "Should update first completion label, but not second as the filter text did not match."
15875                );
15876            }
15877            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15878        }
15879    });
15880}
15881
15882#[gpui::test]
15883async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15884    init_test(cx, |_| {});
15885    let mut cx = EditorLspTestContext::new_rust(
15886        lsp::ServerCapabilities {
15887            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15888            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15889            completion_provider: Some(lsp::CompletionOptions {
15890                resolve_provider: Some(true),
15891                ..Default::default()
15892            }),
15893            ..Default::default()
15894        },
15895        cx,
15896    )
15897    .await;
15898    cx.set_state(indoc! {"
15899        struct TestStruct {
15900            field: i32
15901        }
15902
15903        fn mainˇ() {
15904            let unused_var = 42;
15905            let test_struct = TestStruct { field: 42 };
15906        }
15907    "});
15908    let symbol_range = cx.lsp_range(indoc! {"
15909        struct TestStruct {
15910            field: i32
15911        }
15912
15913        «fn main»() {
15914            let unused_var = 42;
15915            let test_struct = TestStruct { field: 42 };
15916        }
15917    "});
15918    let mut hover_requests =
15919        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15920            Ok(Some(lsp::Hover {
15921                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15922                    kind: lsp::MarkupKind::Markdown,
15923                    value: "Function documentation".to_string(),
15924                }),
15925                range: Some(symbol_range),
15926            }))
15927        });
15928
15929    // Case 1: Test that code action menu hide hover popover
15930    cx.dispatch_action(Hover);
15931    hover_requests.next().await;
15932    cx.condition(|editor, _| editor.hover_state.visible()).await;
15933    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15934        move |_, _, _| async move {
15935            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15936                lsp::CodeAction {
15937                    title: "Remove unused variable".to_string(),
15938                    kind: Some(CodeActionKind::QUICKFIX),
15939                    edit: Some(lsp::WorkspaceEdit {
15940                        changes: Some(
15941                            [(
15942                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15943                                vec![lsp::TextEdit {
15944                                    range: lsp::Range::new(
15945                                        lsp::Position::new(5, 4),
15946                                        lsp::Position::new(5, 27),
15947                                    ),
15948                                    new_text: "".to_string(),
15949                                }],
15950                            )]
15951                            .into_iter()
15952                            .collect(),
15953                        ),
15954                        ..Default::default()
15955                    }),
15956                    ..Default::default()
15957                },
15958            )]))
15959        },
15960    );
15961    cx.update_editor(|editor, window, cx| {
15962        editor.toggle_code_actions(
15963            &ToggleCodeActions {
15964                deployed_from: None,
15965                quick_launch: false,
15966            },
15967            window,
15968            cx,
15969        );
15970    });
15971    code_action_requests.next().await;
15972    cx.run_until_parked();
15973    cx.condition(|editor, _| editor.context_menu_visible())
15974        .await;
15975    cx.update_editor(|editor, _, _| {
15976        assert!(
15977            !editor.hover_state.visible(),
15978            "Hover popover should be hidden when code action menu is shown"
15979        );
15980        // Hide code actions
15981        editor.context_menu.take();
15982    });
15983
15984    // Case 2: Test that code completions hide hover popover
15985    cx.dispatch_action(Hover);
15986    hover_requests.next().await;
15987    cx.condition(|editor, _| editor.hover_state.visible()).await;
15988    let counter = Arc::new(AtomicUsize::new(0));
15989    let mut completion_requests =
15990        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15991            let counter = counter.clone();
15992            async move {
15993                counter.fetch_add(1, atomic::Ordering::Release);
15994                Ok(Some(lsp::CompletionResponse::Array(vec![
15995                    lsp::CompletionItem {
15996                        label: "main".into(),
15997                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15998                        detail: Some("() -> ()".to_string()),
15999                        ..Default::default()
16000                    },
16001                    lsp::CompletionItem {
16002                        label: "TestStruct".into(),
16003                        kind: Some(lsp::CompletionItemKind::STRUCT),
16004                        detail: Some("struct TestStruct".to_string()),
16005                        ..Default::default()
16006                    },
16007                ])))
16008            }
16009        });
16010    cx.update_editor(|editor, window, cx| {
16011        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16012    });
16013    completion_requests.next().await;
16014    cx.condition(|editor, _| editor.context_menu_visible())
16015        .await;
16016    cx.update_editor(|editor, _, _| {
16017        assert!(
16018            !editor.hover_state.visible(),
16019            "Hover popover should be hidden when completion menu is shown"
16020        );
16021    });
16022}
16023
16024#[gpui::test]
16025async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16026    init_test(cx, |_| {});
16027
16028    let mut cx = EditorLspTestContext::new_rust(
16029        lsp::ServerCapabilities {
16030            completion_provider: Some(lsp::CompletionOptions {
16031                trigger_characters: Some(vec![".".to_string()]),
16032                resolve_provider: Some(true),
16033                ..Default::default()
16034            }),
16035            ..Default::default()
16036        },
16037        cx,
16038    )
16039    .await;
16040
16041    cx.set_state("fn main() { let a = 2ˇ; }");
16042    cx.simulate_keystroke(".");
16043
16044    let unresolved_item_1 = lsp::CompletionItem {
16045        label: "id".to_string(),
16046        filter_text: Some("id".to_string()),
16047        detail: None,
16048        documentation: None,
16049        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16050            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16051            new_text: ".id".to_string(),
16052        })),
16053        ..lsp::CompletionItem::default()
16054    };
16055    let resolved_item_1 = lsp::CompletionItem {
16056        additional_text_edits: Some(vec![lsp::TextEdit {
16057            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16058            new_text: "!!".to_string(),
16059        }]),
16060        ..unresolved_item_1.clone()
16061    };
16062    let unresolved_item_2 = lsp::CompletionItem {
16063        label: "other".to_string(),
16064        filter_text: Some("other".to_string()),
16065        detail: None,
16066        documentation: None,
16067        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16068            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16069            new_text: ".other".to_string(),
16070        })),
16071        ..lsp::CompletionItem::default()
16072    };
16073    let resolved_item_2 = lsp::CompletionItem {
16074        additional_text_edits: Some(vec![lsp::TextEdit {
16075            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16076            new_text: "??".to_string(),
16077        }]),
16078        ..unresolved_item_2.clone()
16079    };
16080
16081    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16082    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16083    cx.lsp
16084        .server
16085        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16086            let unresolved_item_1 = unresolved_item_1.clone();
16087            let resolved_item_1 = resolved_item_1.clone();
16088            let unresolved_item_2 = unresolved_item_2.clone();
16089            let resolved_item_2 = resolved_item_2.clone();
16090            let resolve_requests_1 = resolve_requests_1.clone();
16091            let resolve_requests_2 = resolve_requests_2.clone();
16092            move |unresolved_request, _| {
16093                let unresolved_item_1 = unresolved_item_1.clone();
16094                let resolved_item_1 = resolved_item_1.clone();
16095                let unresolved_item_2 = unresolved_item_2.clone();
16096                let resolved_item_2 = resolved_item_2.clone();
16097                let resolve_requests_1 = resolve_requests_1.clone();
16098                let resolve_requests_2 = resolve_requests_2.clone();
16099                async move {
16100                    if unresolved_request == unresolved_item_1 {
16101                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16102                        Ok(resolved_item_1.clone())
16103                    } else if unresolved_request == unresolved_item_2 {
16104                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16105                        Ok(resolved_item_2.clone())
16106                    } else {
16107                        panic!("Unexpected completion item {unresolved_request:?}")
16108                    }
16109                }
16110            }
16111        })
16112        .detach();
16113
16114    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16115        let unresolved_item_1 = unresolved_item_1.clone();
16116        let unresolved_item_2 = unresolved_item_2.clone();
16117        async move {
16118            Ok(Some(lsp::CompletionResponse::Array(vec![
16119                unresolved_item_1,
16120                unresolved_item_2,
16121            ])))
16122        }
16123    })
16124    .next()
16125    .await;
16126
16127    cx.condition(|editor, _| editor.context_menu_visible())
16128        .await;
16129    cx.update_editor(|editor, _, _| {
16130        let context_menu = editor.context_menu.borrow_mut();
16131        let context_menu = context_menu
16132            .as_ref()
16133            .expect("Should have the context menu deployed");
16134        match context_menu {
16135            CodeContextMenu::Completions(completions_menu) => {
16136                let completions = completions_menu.completions.borrow_mut();
16137                assert_eq!(
16138                    completions
16139                        .iter()
16140                        .map(|completion| &completion.label.text)
16141                        .collect::<Vec<_>>(),
16142                    vec!["id", "other"]
16143                )
16144            }
16145            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16146        }
16147    });
16148    cx.run_until_parked();
16149
16150    cx.update_editor(|editor, window, cx| {
16151        editor.context_menu_next(&ContextMenuNext, window, cx);
16152    });
16153    cx.run_until_parked();
16154    cx.update_editor(|editor, window, cx| {
16155        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16156    });
16157    cx.run_until_parked();
16158    cx.update_editor(|editor, window, cx| {
16159        editor.context_menu_next(&ContextMenuNext, window, cx);
16160    });
16161    cx.run_until_parked();
16162    cx.update_editor(|editor, window, cx| {
16163        editor
16164            .compose_completion(&ComposeCompletion::default(), window, cx)
16165            .expect("No task returned")
16166    })
16167    .await
16168    .expect("Completion failed");
16169    cx.run_until_parked();
16170
16171    cx.update_editor(|editor, _, cx| {
16172        assert_eq!(
16173            resolve_requests_1.load(atomic::Ordering::Acquire),
16174            1,
16175            "Should always resolve once despite multiple selections"
16176        );
16177        assert_eq!(
16178            resolve_requests_2.load(atomic::Ordering::Acquire),
16179            1,
16180            "Should always resolve once after multiple selections and applying the completion"
16181        );
16182        assert_eq!(
16183            editor.text(cx),
16184            "fn main() { let a = ??.other; }",
16185            "Should use resolved data when applying the completion"
16186        );
16187    });
16188}
16189
16190#[gpui::test]
16191async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16192    init_test(cx, |_| {});
16193
16194    let item_0 = lsp::CompletionItem {
16195        label: "abs".into(),
16196        insert_text: Some("abs".into()),
16197        data: Some(json!({ "very": "special"})),
16198        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16199        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16200            lsp::InsertReplaceEdit {
16201                new_text: "abs".to_string(),
16202                insert: lsp::Range::default(),
16203                replace: lsp::Range::default(),
16204            },
16205        )),
16206        ..lsp::CompletionItem::default()
16207    };
16208    let items = iter::once(item_0.clone())
16209        .chain((11..51).map(|i| lsp::CompletionItem {
16210            label: format!("item_{}", i),
16211            insert_text: Some(format!("item_{}", i)),
16212            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16213            ..lsp::CompletionItem::default()
16214        }))
16215        .collect::<Vec<_>>();
16216
16217    let default_commit_characters = vec!["?".to_string()];
16218    let default_data = json!({ "default": "data"});
16219    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16220    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16221    let default_edit_range = lsp::Range {
16222        start: lsp::Position {
16223            line: 0,
16224            character: 5,
16225        },
16226        end: lsp::Position {
16227            line: 0,
16228            character: 5,
16229        },
16230    };
16231
16232    let mut cx = EditorLspTestContext::new_rust(
16233        lsp::ServerCapabilities {
16234            completion_provider: Some(lsp::CompletionOptions {
16235                trigger_characters: Some(vec![".".to_string()]),
16236                resolve_provider: Some(true),
16237                ..Default::default()
16238            }),
16239            ..Default::default()
16240        },
16241        cx,
16242    )
16243    .await;
16244
16245    cx.set_state("fn main() { let a = 2ˇ; }");
16246    cx.simulate_keystroke(".");
16247
16248    let completion_data = default_data.clone();
16249    let completion_characters = default_commit_characters.clone();
16250    let completion_items = items.clone();
16251    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16252        let default_data = completion_data.clone();
16253        let default_commit_characters = completion_characters.clone();
16254        let items = completion_items.clone();
16255        async move {
16256            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16257                items,
16258                item_defaults: Some(lsp::CompletionListItemDefaults {
16259                    data: Some(default_data.clone()),
16260                    commit_characters: Some(default_commit_characters.clone()),
16261                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16262                        default_edit_range,
16263                    )),
16264                    insert_text_format: Some(default_insert_text_format),
16265                    insert_text_mode: Some(default_insert_text_mode),
16266                }),
16267                ..lsp::CompletionList::default()
16268            })))
16269        }
16270    })
16271    .next()
16272    .await;
16273
16274    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16275    cx.lsp
16276        .server
16277        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16278            let closure_resolved_items = resolved_items.clone();
16279            move |item_to_resolve, _| {
16280                let closure_resolved_items = closure_resolved_items.clone();
16281                async move {
16282                    closure_resolved_items.lock().push(item_to_resolve.clone());
16283                    Ok(item_to_resolve)
16284                }
16285            }
16286        })
16287        .detach();
16288
16289    cx.condition(|editor, _| editor.context_menu_visible())
16290        .await;
16291    cx.run_until_parked();
16292    cx.update_editor(|editor, _, _| {
16293        let menu = editor.context_menu.borrow_mut();
16294        match menu.as_ref().expect("should have the completions menu") {
16295            CodeContextMenu::Completions(completions_menu) => {
16296                assert_eq!(
16297                    completions_menu
16298                        .entries
16299                        .borrow()
16300                        .iter()
16301                        .map(|mat| mat.string.clone())
16302                        .collect::<Vec<String>>(),
16303                    items
16304                        .iter()
16305                        .map(|completion| completion.label.clone())
16306                        .collect::<Vec<String>>()
16307                );
16308            }
16309            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16310        }
16311    });
16312    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16313    // with 4 from the end.
16314    assert_eq!(
16315        *resolved_items.lock(),
16316        [&items[0..16], &items[items.len() - 4..items.len()]]
16317            .concat()
16318            .iter()
16319            .cloned()
16320            .map(|mut item| {
16321                if item.data.is_none() {
16322                    item.data = Some(default_data.clone());
16323                }
16324                item
16325            })
16326            .collect::<Vec<lsp::CompletionItem>>(),
16327        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16328    );
16329    resolved_items.lock().clear();
16330
16331    cx.update_editor(|editor, window, cx| {
16332        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16333    });
16334    cx.run_until_parked();
16335    // Completions that have already been resolved are skipped.
16336    assert_eq!(
16337        *resolved_items.lock(),
16338        items[items.len() - 17..items.len() - 4]
16339            .iter()
16340            .cloned()
16341            .map(|mut item| {
16342                if item.data.is_none() {
16343                    item.data = Some(default_data.clone());
16344                }
16345                item
16346            })
16347            .collect::<Vec<lsp::CompletionItem>>()
16348    );
16349    resolved_items.lock().clear();
16350}
16351
16352#[gpui::test]
16353async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16354    init_test(cx, |_| {});
16355
16356    let mut cx = EditorLspTestContext::new(
16357        Language::new(
16358            LanguageConfig {
16359                matcher: LanguageMatcher {
16360                    path_suffixes: vec!["jsx".into()],
16361                    ..Default::default()
16362                },
16363                overrides: [(
16364                    "element".into(),
16365                    LanguageConfigOverride {
16366                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16367                        ..Default::default()
16368                    },
16369                )]
16370                .into_iter()
16371                .collect(),
16372                ..Default::default()
16373            },
16374            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16375        )
16376        .with_override_query("(jsx_self_closing_element) @element")
16377        .unwrap(),
16378        lsp::ServerCapabilities {
16379            completion_provider: Some(lsp::CompletionOptions {
16380                trigger_characters: Some(vec![":".to_string()]),
16381                ..Default::default()
16382            }),
16383            ..Default::default()
16384        },
16385        cx,
16386    )
16387    .await;
16388
16389    cx.lsp
16390        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16391            Ok(Some(lsp::CompletionResponse::Array(vec![
16392                lsp::CompletionItem {
16393                    label: "bg-blue".into(),
16394                    ..Default::default()
16395                },
16396                lsp::CompletionItem {
16397                    label: "bg-red".into(),
16398                    ..Default::default()
16399                },
16400                lsp::CompletionItem {
16401                    label: "bg-yellow".into(),
16402                    ..Default::default()
16403                },
16404            ])))
16405        });
16406
16407    cx.set_state(r#"<p class="bgˇ" />"#);
16408
16409    // Trigger completion when typing a dash, because the dash is an extra
16410    // word character in the 'element' scope, which contains the cursor.
16411    cx.simulate_keystroke("-");
16412    cx.executor().run_until_parked();
16413    cx.update_editor(|editor, _, _| {
16414        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16415        {
16416            assert_eq!(
16417                completion_menu_entries(&menu),
16418                &["bg-blue", "bg-red", "bg-yellow"]
16419            );
16420        } else {
16421            panic!("expected completion menu to be open");
16422        }
16423    });
16424
16425    cx.simulate_keystroke("l");
16426    cx.executor().run_until_parked();
16427    cx.update_editor(|editor, _, _| {
16428        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16429        {
16430            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16431        } else {
16432            panic!("expected completion menu to be open");
16433        }
16434    });
16435
16436    // When filtering completions, consider the character after the '-' to
16437    // be the start of a subword.
16438    cx.set_state(r#"<p class="yelˇ" />"#);
16439    cx.simulate_keystroke("l");
16440    cx.executor().run_until_parked();
16441    cx.update_editor(|editor, _, _| {
16442        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16443        {
16444            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16445        } else {
16446            panic!("expected completion menu to be open");
16447        }
16448    });
16449}
16450
16451fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16452    let entries = menu.entries.borrow();
16453    entries.iter().map(|mat| mat.string.clone()).collect()
16454}
16455
16456#[gpui::test]
16457async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16458    init_test(cx, |settings| {
16459        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16460            Formatter::Prettier,
16461        )))
16462    });
16463
16464    let fs = FakeFs::new(cx.executor());
16465    fs.insert_file(path!("/file.ts"), Default::default()).await;
16466
16467    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16468    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16469
16470    language_registry.add(Arc::new(Language::new(
16471        LanguageConfig {
16472            name: "TypeScript".into(),
16473            matcher: LanguageMatcher {
16474                path_suffixes: vec!["ts".to_string()],
16475                ..Default::default()
16476            },
16477            ..Default::default()
16478        },
16479        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16480    )));
16481    update_test_language_settings(cx, |settings| {
16482        settings.defaults.prettier = Some(PrettierSettings {
16483            allowed: true,
16484            ..PrettierSettings::default()
16485        });
16486    });
16487
16488    let test_plugin = "test_plugin";
16489    let _ = language_registry.register_fake_lsp(
16490        "TypeScript",
16491        FakeLspAdapter {
16492            prettier_plugins: vec![test_plugin],
16493            ..Default::default()
16494        },
16495    );
16496
16497    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16498    let buffer = project
16499        .update(cx, |project, cx| {
16500            project.open_local_buffer(path!("/file.ts"), cx)
16501        })
16502        .await
16503        .unwrap();
16504
16505    let buffer_text = "one\ntwo\nthree\n";
16506    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16507    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16508    editor.update_in(cx, |editor, window, cx| {
16509        editor.set_text(buffer_text, window, cx)
16510    });
16511
16512    editor
16513        .update_in(cx, |editor, window, cx| {
16514            editor.perform_format(
16515                project.clone(),
16516                FormatTrigger::Manual,
16517                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16518                window,
16519                cx,
16520            )
16521        })
16522        .unwrap()
16523        .await;
16524    assert_eq!(
16525        editor.update(cx, |editor, cx| editor.text(cx)),
16526        buffer_text.to_string() + prettier_format_suffix,
16527        "Test prettier formatting was not applied to the original buffer text",
16528    );
16529
16530    update_test_language_settings(cx, |settings| {
16531        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16532    });
16533    let format = editor.update_in(cx, |editor, window, cx| {
16534        editor.perform_format(
16535            project.clone(),
16536            FormatTrigger::Manual,
16537            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16538            window,
16539            cx,
16540        )
16541    });
16542    format.await.unwrap();
16543    assert_eq!(
16544        editor.update(cx, |editor, cx| editor.text(cx)),
16545        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16546        "Autoformatting (via test prettier) was not applied to the original buffer text",
16547    );
16548}
16549
16550#[gpui::test]
16551async fn test_addition_reverts(cx: &mut TestAppContext) {
16552    init_test(cx, |_| {});
16553    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16554    let base_text = indoc! {r#"
16555        struct Row;
16556        struct Row1;
16557        struct Row2;
16558
16559        struct Row4;
16560        struct Row5;
16561        struct Row6;
16562
16563        struct Row8;
16564        struct Row9;
16565        struct Row10;"#};
16566
16567    // When addition hunks are not adjacent to carets, no hunk revert is performed
16568    assert_hunk_revert(
16569        indoc! {r#"struct Row;
16570                   struct Row1;
16571                   struct Row1.1;
16572                   struct Row1.2;
16573                   struct Row2;ˇ
16574
16575                   struct Row4;
16576                   struct Row5;
16577                   struct Row6;
16578
16579                   struct Row8;
16580                   ˇstruct Row9;
16581                   struct Row9.1;
16582                   struct Row9.2;
16583                   struct Row9.3;
16584                   struct Row10;"#},
16585        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16586        indoc! {r#"struct Row;
16587                   struct Row1;
16588                   struct Row1.1;
16589                   struct Row1.2;
16590                   struct Row2;ˇ
16591
16592                   struct Row4;
16593                   struct Row5;
16594                   struct Row6;
16595
16596                   struct Row8;
16597                   ˇstruct Row9;
16598                   struct Row9.1;
16599                   struct Row9.2;
16600                   struct Row9.3;
16601                   struct Row10;"#},
16602        base_text,
16603        &mut cx,
16604    );
16605    // Same for selections
16606    assert_hunk_revert(
16607        indoc! {r#"struct Row;
16608                   struct Row1;
16609                   struct Row2;
16610                   struct Row2.1;
16611                   struct Row2.2;
16612                   «ˇ
16613                   struct Row4;
16614                   struct» Row5;
16615                   «struct Row6;
16616                   ˇ»
16617                   struct Row9.1;
16618                   struct Row9.2;
16619                   struct Row9.3;
16620                   struct Row8;
16621                   struct Row9;
16622                   struct Row10;"#},
16623        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16624        indoc! {r#"struct Row;
16625                   struct Row1;
16626                   struct Row2;
16627                   struct Row2.1;
16628                   struct Row2.2;
16629                   «ˇ
16630                   struct Row4;
16631                   struct» Row5;
16632                   «struct Row6;
16633                   ˇ»
16634                   struct Row9.1;
16635                   struct Row9.2;
16636                   struct Row9.3;
16637                   struct Row8;
16638                   struct Row9;
16639                   struct Row10;"#},
16640        base_text,
16641        &mut cx,
16642    );
16643
16644    // When carets and selections intersect the addition hunks, those are reverted.
16645    // Adjacent carets got merged.
16646    assert_hunk_revert(
16647        indoc! {r#"struct Row;
16648                   ˇ// something on the top
16649                   struct Row1;
16650                   struct Row2;
16651                   struct Roˇw3.1;
16652                   struct Row2.2;
16653                   struct Row2.3;ˇ
16654
16655                   struct Row4;
16656                   struct ˇRow5.1;
16657                   struct Row5.2;
16658                   struct «Rowˇ»5.3;
16659                   struct Row5;
16660                   struct Row6;
16661                   ˇ
16662                   struct Row9.1;
16663                   struct «Rowˇ»9.2;
16664                   struct «ˇRow»9.3;
16665                   struct Row8;
16666                   struct Row9;
16667                   «ˇ// something on bottom»
16668                   struct Row10;"#},
16669        vec![
16670            DiffHunkStatusKind::Added,
16671            DiffHunkStatusKind::Added,
16672            DiffHunkStatusKind::Added,
16673            DiffHunkStatusKind::Added,
16674            DiffHunkStatusKind::Added,
16675        ],
16676        indoc! {r#"struct Row;
16677                   ˇstruct Row1;
16678                   struct Row2;
16679                   ˇ
16680                   struct Row4;
16681                   ˇstruct Row5;
16682                   struct Row6;
16683                   ˇ
16684                   ˇstruct Row8;
16685                   struct Row9;
16686                   ˇstruct Row10;"#},
16687        base_text,
16688        &mut cx,
16689    );
16690}
16691
16692#[gpui::test]
16693async fn test_modification_reverts(cx: &mut TestAppContext) {
16694    init_test(cx, |_| {});
16695    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16696    let base_text = indoc! {r#"
16697        struct Row;
16698        struct Row1;
16699        struct Row2;
16700
16701        struct Row4;
16702        struct Row5;
16703        struct Row6;
16704
16705        struct Row8;
16706        struct Row9;
16707        struct Row10;"#};
16708
16709    // Modification hunks behave the same as the addition ones.
16710    assert_hunk_revert(
16711        indoc! {r#"struct Row;
16712                   struct Row1;
16713                   struct Row33;
16714                   ˇ
16715                   struct Row4;
16716                   struct Row5;
16717                   struct Row6;
16718                   ˇ
16719                   struct Row99;
16720                   struct Row9;
16721                   struct Row10;"#},
16722        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16723        indoc! {r#"struct Row;
16724                   struct Row1;
16725                   struct Row33;
16726                   ˇ
16727                   struct Row4;
16728                   struct Row5;
16729                   struct Row6;
16730                   ˇ
16731                   struct Row99;
16732                   struct Row9;
16733                   struct Row10;"#},
16734        base_text,
16735        &mut cx,
16736    );
16737    assert_hunk_revert(
16738        indoc! {r#"struct Row;
16739                   struct Row1;
16740                   struct Row33;
16741                   «ˇ
16742                   struct Row4;
16743                   struct» Row5;
16744                   «struct Row6;
16745                   ˇ»
16746                   struct Row99;
16747                   struct Row9;
16748                   struct Row10;"#},
16749        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16750        indoc! {r#"struct Row;
16751                   struct Row1;
16752                   struct Row33;
16753                   «ˇ
16754                   struct Row4;
16755                   struct» Row5;
16756                   «struct Row6;
16757                   ˇ»
16758                   struct Row99;
16759                   struct Row9;
16760                   struct Row10;"#},
16761        base_text,
16762        &mut cx,
16763    );
16764
16765    assert_hunk_revert(
16766        indoc! {r#"ˇstruct Row1.1;
16767                   struct Row1;
16768                   «ˇstr»uct Row22;
16769
16770                   struct ˇRow44;
16771                   struct Row5;
16772                   struct «Rˇ»ow66;ˇ
16773
16774                   «struˇ»ct Row88;
16775                   struct Row9;
16776                   struct Row1011;ˇ"#},
16777        vec![
16778            DiffHunkStatusKind::Modified,
16779            DiffHunkStatusKind::Modified,
16780            DiffHunkStatusKind::Modified,
16781            DiffHunkStatusKind::Modified,
16782            DiffHunkStatusKind::Modified,
16783            DiffHunkStatusKind::Modified,
16784        ],
16785        indoc! {r#"struct Row;
16786                   ˇstruct Row1;
16787                   struct Row2;
16788                   ˇ
16789                   struct Row4;
16790                   ˇstruct Row5;
16791                   struct Row6;
16792                   ˇ
16793                   struct Row8;
16794                   ˇstruct Row9;
16795                   struct Row10;ˇ"#},
16796        base_text,
16797        &mut cx,
16798    );
16799}
16800
16801#[gpui::test]
16802async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16803    init_test(cx, |_| {});
16804    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16805    let base_text = indoc! {r#"
16806        one
16807
16808        two
16809        three
16810        "#};
16811
16812    cx.set_head_text(base_text);
16813    cx.set_state("\nˇ\n");
16814    cx.executor().run_until_parked();
16815    cx.update_editor(|editor, _window, cx| {
16816        editor.expand_selected_diff_hunks(cx);
16817    });
16818    cx.executor().run_until_parked();
16819    cx.update_editor(|editor, window, cx| {
16820        editor.backspace(&Default::default(), window, cx);
16821    });
16822    cx.run_until_parked();
16823    cx.assert_state_with_diff(
16824        indoc! {r#"
16825
16826        - two
16827        - threeˇ
16828        +
16829        "#}
16830        .to_string(),
16831    );
16832}
16833
16834#[gpui::test]
16835async fn test_deletion_reverts(cx: &mut TestAppContext) {
16836    init_test(cx, |_| {});
16837    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16838    let base_text = indoc! {r#"struct Row;
16839struct Row1;
16840struct Row2;
16841
16842struct Row4;
16843struct Row5;
16844struct Row6;
16845
16846struct Row8;
16847struct Row9;
16848struct Row10;"#};
16849
16850    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16851    assert_hunk_revert(
16852        indoc! {r#"struct Row;
16853                   struct Row2;
16854
16855                   ˇstruct Row4;
16856                   struct Row5;
16857                   struct Row6;
16858                   ˇ
16859                   struct Row8;
16860                   struct Row10;"#},
16861        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16862        indoc! {r#"struct Row;
16863                   struct Row2;
16864
16865                   ˇstruct Row4;
16866                   struct Row5;
16867                   struct Row6;
16868                   ˇ
16869                   struct Row8;
16870                   struct Row10;"#},
16871        base_text,
16872        &mut cx,
16873    );
16874    assert_hunk_revert(
16875        indoc! {r#"struct Row;
16876                   struct Row2;
16877
16878                   «ˇstruct Row4;
16879                   struct» Row5;
16880                   «struct Row6;
16881                   ˇ»
16882                   struct Row8;
16883                   struct Row10;"#},
16884        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16885        indoc! {r#"struct Row;
16886                   struct Row2;
16887
16888                   «ˇstruct Row4;
16889                   struct» Row5;
16890                   «struct Row6;
16891                   ˇ»
16892                   struct Row8;
16893                   struct Row10;"#},
16894        base_text,
16895        &mut cx,
16896    );
16897
16898    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16899    assert_hunk_revert(
16900        indoc! {r#"struct Row;
16901                   ˇstruct Row2;
16902
16903                   struct Row4;
16904                   struct Row5;
16905                   struct Row6;
16906
16907                   struct Row8;ˇ
16908                   struct Row10;"#},
16909        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16910        indoc! {r#"struct Row;
16911                   struct Row1;
16912                   ˇstruct Row2;
16913
16914                   struct Row4;
16915                   struct Row5;
16916                   struct Row6;
16917
16918                   struct Row8;ˇ
16919                   struct Row9;
16920                   struct Row10;"#},
16921        base_text,
16922        &mut cx,
16923    );
16924    assert_hunk_revert(
16925        indoc! {r#"struct Row;
16926                   struct Row2«ˇ;
16927                   struct Row4;
16928                   struct» Row5;
16929                   «struct Row6;
16930
16931                   struct Row8;ˇ»
16932                   struct Row10;"#},
16933        vec![
16934            DiffHunkStatusKind::Deleted,
16935            DiffHunkStatusKind::Deleted,
16936            DiffHunkStatusKind::Deleted,
16937        ],
16938        indoc! {r#"struct Row;
16939                   struct Row1;
16940                   struct Row2«ˇ;
16941
16942                   struct Row4;
16943                   struct» Row5;
16944                   «struct Row6;
16945
16946                   struct Row8;ˇ»
16947                   struct Row9;
16948                   struct Row10;"#},
16949        base_text,
16950        &mut cx,
16951    );
16952}
16953
16954#[gpui::test]
16955async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16956    init_test(cx, |_| {});
16957
16958    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16959    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16960    let base_text_3 =
16961        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16962
16963    let text_1 = edit_first_char_of_every_line(base_text_1);
16964    let text_2 = edit_first_char_of_every_line(base_text_2);
16965    let text_3 = edit_first_char_of_every_line(base_text_3);
16966
16967    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16968    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16969    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16970
16971    let multibuffer = cx.new(|cx| {
16972        let mut multibuffer = MultiBuffer::new(ReadWrite);
16973        multibuffer.push_excerpts(
16974            buffer_1.clone(),
16975            [
16976                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16977                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16978                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16979            ],
16980            cx,
16981        );
16982        multibuffer.push_excerpts(
16983            buffer_2.clone(),
16984            [
16985                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16986                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16987                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16988            ],
16989            cx,
16990        );
16991        multibuffer.push_excerpts(
16992            buffer_3.clone(),
16993            [
16994                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16995                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16996                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16997            ],
16998            cx,
16999        );
17000        multibuffer
17001    });
17002
17003    let fs = FakeFs::new(cx.executor());
17004    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17005    let (editor, cx) = cx
17006        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17007    editor.update_in(cx, |editor, _window, cx| {
17008        for (buffer, diff_base) in [
17009            (buffer_1.clone(), base_text_1),
17010            (buffer_2.clone(), base_text_2),
17011            (buffer_3.clone(), base_text_3),
17012        ] {
17013            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17014            editor
17015                .buffer
17016                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17017        }
17018    });
17019    cx.executor().run_until_parked();
17020
17021    editor.update_in(cx, |editor, window, cx| {
17022        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}");
17023        editor.select_all(&SelectAll, window, cx);
17024        editor.git_restore(&Default::default(), window, cx);
17025    });
17026    cx.executor().run_until_parked();
17027
17028    // When all ranges are selected, all buffer hunks are reverted.
17029    editor.update(cx, |editor, cx| {
17030        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");
17031    });
17032    buffer_1.update(cx, |buffer, _| {
17033        assert_eq!(buffer.text(), base_text_1);
17034    });
17035    buffer_2.update(cx, |buffer, _| {
17036        assert_eq!(buffer.text(), base_text_2);
17037    });
17038    buffer_3.update(cx, |buffer, _| {
17039        assert_eq!(buffer.text(), base_text_3);
17040    });
17041
17042    editor.update_in(cx, |editor, window, cx| {
17043        editor.undo(&Default::default(), window, cx);
17044    });
17045
17046    editor.update_in(cx, |editor, window, cx| {
17047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17048            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17049        });
17050        editor.git_restore(&Default::default(), window, cx);
17051    });
17052
17053    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17054    // but not affect buffer_2 and its related excerpts.
17055    editor.update(cx, |editor, cx| {
17056        assert_eq!(
17057            editor.text(cx),
17058            "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}"
17059        );
17060    });
17061    buffer_1.update(cx, |buffer, _| {
17062        assert_eq!(buffer.text(), base_text_1);
17063    });
17064    buffer_2.update(cx, |buffer, _| {
17065        assert_eq!(
17066            buffer.text(),
17067            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17068        );
17069    });
17070    buffer_3.update(cx, |buffer, _| {
17071        assert_eq!(
17072            buffer.text(),
17073            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17074        );
17075    });
17076
17077    fn edit_first_char_of_every_line(text: &str) -> String {
17078        text.split('\n')
17079            .map(|line| format!("X{}", &line[1..]))
17080            .collect::<Vec<_>>()
17081            .join("\n")
17082    }
17083}
17084
17085#[gpui::test]
17086async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17087    init_test(cx, |_| {});
17088
17089    let cols = 4;
17090    let rows = 10;
17091    let sample_text_1 = sample_text(rows, cols, 'a');
17092    assert_eq!(
17093        sample_text_1,
17094        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17095    );
17096    let sample_text_2 = sample_text(rows, cols, 'l');
17097    assert_eq!(
17098        sample_text_2,
17099        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17100    );
17101    let sample_text_3 = sample_text(rows, cols, 'v');
17102    assert_eq!(
17103        sample_text_3,
17104        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17105    );
17106
17107    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17108    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17109    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17110
17111    let multi_buffer = cx.new(|cx| {
17112        let mut multibuffer = MultiBuffer::new(ReadWrite);
17113        multibuffer.push_excerpts(
17114            buffer_1.clone(),
17115            [
17116                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17117                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17118                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17119            ],
17120            cx,
17121        );
17122        multibuffer.push_excerpts(
17123            buffer_2.clone(),
17124            [
17125                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17126                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17127                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17128            ],
17129            cx,
17130        );
17131        multibuffer.push_excerpts(
17132            buffer_3.clone(),
17133            [
17134                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17135                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17136                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17137            ],
17138            cx,
17139        );
17140        multibuffer
17141    });
17142
17143    let fs = FakeFs::new(cx.executor());
17144    fs.insert_tree(
17145        "/a",
17146        json!({
17147            "main.rs": sample_text_1,
17148            "other.rs": sample_text_2,
17149            "lib.rs": sample_text_3,
17150        }),
17151    )
17152    .await;
17153    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17154    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17155    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17156    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17157        Editor::new(
17158            EditorMode::full(),
17159            multi_buffer,
17160            Some(project.clone()),
17161            window,
17162            cx,
17163        )
17164    });
17165    let multibuffer_item_id = workspace
17166        .update(cx, |workspace, window, cx| {
17167            assert!(
17168                workspace.active_item(cx).is_none(),
17169                "active item should be None before the first item is added"
17170            );
17171            workspace.add_item_to_active_pane(
17172                Box::new(multi_buffer_editor.clone()),
17173                None,
17174                true,
17175                window,
17176                cx,
17177            );
17178            let active_item = workspace
17179                .active_item(cx)
17180                .expect("should have an active item after adding the multi buffer");
17181            assert!(
17182                !active_item.is_singleton(cx),
17183                "A multi buffer was expected to active after adding"
17184            );
17185            active_item.item_id()
17186        })
17187        .unwrap();
17188    cx.executor().run_until_parked();
17189
17190    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17191        editor.change_selections(
17192            SelectionEffects::scroll(Autoscroll::Next),
17193            window,
17194            cx,
17195            |s| s.select_ranges(Some(1..2)),
17196        );
17197        editor.open_excerpts(&OpenExcerpts, window, cx);
17198    });
17199    cx.executor().run_until_parked();
17200    let first_item_id = workspace
17201        .update(cx, |workspace, window, cx| {
17202            let active_item = workspace
17203                .active_item(cx)
17204                .expect("should have an active item after navigating into the 1st buffer");
17205            let first_item_id = active_item.item_id();
17206            assert_ne!(
17207                first_item_id, multibuffer_item_id,
17208                "Should navigate into the 1st buffer and activate it"
17209            );
17210            assert!(
17211                active_item.is_singleton(cx),
17212                "New active item should be a singleton buffer"
17213            );
17214            assert_eq!(
17215                active_item
17216                    .act_as::<Editor>(cx)
17217                    .expect("should have navigated into an editor for the 1st buffer")
17218                    .read(cx)
17219                    .text(cx),
17220                sample_text_1
17221            );
17222
17223            workspace
17224                .go_back(workspace.active_pane().downgrade(), window, cx)
17225                .detach_and_log_err(cx);
17226
17227            first_item_id
17228        })
17229        .unwrap();
17230    cx.executor().run_until_parked();
17231    workspace
17232        .update(cx, |workspace, _, cx| {
17233            let active_item = workspace
17234                .active_item(cx)
17235                .expect("should have an active item after navigating back");
17236            assert_eq!(
17237                active_item.item_id(),
17238                multibuffer_item_id,
17239                "Should navigate back to the multi buffer"
17240            );
17241            assert!(!active_item.is_singleton(cx));
17242        })
17243        .unwrap();
17244
17245    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17246        editor.change_selections(
17247            SelectionEffects::scroll(Autoscroll::Next),
17248            window,
17249            cx,
17250            |s| s.select_ranges(Some(39..40)),
17251        );
17252        editor.open_excerpts(&OpenExcerpts, window, cx);
17253    });
17254    cx.executor().run_until_parked();
17255    let second_item_id = workspace
17256        .update(cx, |workspace, window, cx| {
17257            let active_item = workspace
17258                .active_item(cx)
17259                .expect("should have an active item after navigating into the 2nd buffer");
17260            let second_item_id = active_item.item_id();
17261            assert_ne!(
17262                second_item_id, multibuffer_item_id,
17263                "Should navigate away from the multibuffer"
17264            );
17265            assert_ne!(
17266                second_item_id, first_item_id,
17267                "Should navigate into the 2nd buffer and activate it"
17268            );
17269            assert!(
17270                active_item.is_singleton(cx),
17271                "New active item should be a singleton buffer"
17272            );
17273            assert_eq!(
17274                active_item
17275                    .act_as::<Editor>(cx)
17276                    .expect("should have navigated into an editor")
17277                    .read(cx)
17278                    .text(cx),
17279                sample_text_2
17280            );
17281
17282            workspace
17283                .go_back(workspace.active_pane().downgrade(), window, cx)
17284                .detach_and_log_err(cx);
17285
17286            second_item_id
17287        })
17288        .unwrap();
17289    cx.executor().run_until_parked();
17290    workspace
17291        .update(cx, |workspace, _, cx| {
17292            let active_item = workspace
17293                .active_item(cx)
17294                .expect("should have an active item after navigating back from the 2nd buffer");
17295            assert_eq!(
17296                active_item.item_id(),
17297                multibuffer_item_id,
17298                "Should navigate back from the 2nd buffer to the multi buffer"
17299            );
17300            assert!(!active_item.is_singleton(cx));
17301        })
17302        .unwrap();
17303
17304    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17305        editor.change_selections(
17306            SelectionEffects::scroll(Autoscroll::Next),
17307            window,
17308            cx,
17309            |s| s.select_ranges(Some(70..70)),
17310        );
17311        editor.open_excerpts(&OpenExcerpts, window, cx);
17312    });
17313    cx.executor().run_until_parked();
17314    workspace
17315        .update(cx, |workspace, window, cx| {
17316            let active_item = workspace
17317                .active_item(cx)
17318                .expect("should have an active item after navigating into the 3rd buffer");
17319            let third_item_id = active_item.item_id();
17320            assert_ne!(
17321                third_item_id, multibuffer_item_id,
17322                "Should navigate into the 3rd buffer and activate it"
17323            );
17324            assert_ne!(third_item_id, first_item_id);
17325            assert_ne!(third_item_id, second_item_id);
17326            assert!(
17327                active_item.is_singleton(cx),
17328                "New active item should be a singleton buffer"
17329            );
17330            assert_eq!(
17331                active_item
17332                    .act_as::<Editor>(cx)
17333                    .expect("should have navigated into an editor")
17334                    .read(cx)
17335                    .text(cx),
17336                sample_text_3
17337            );
17338
17339            workspace
17340                .go_back(workspace.active_pane().downgrade(), window, cx)
17341                .detach_and_log_err(cx);
17342        })
17343        .unwrap();
17344    cx.executor().run_until_parked();
17345    workspace
17346        .update(cx, |workspace, _, cx| {
17347            let active_item = workspace
17348                .active_item(cx)
17349                .expect("should have an active item after navigating back from the 3rd buffer");
17350            assert_eq!(
17351                active_item.item_id(),
17352                multibuffer_item_id,
17353                "Should navigate back from the 3rd buffer to the multi buffer"
17354            );
17355            assert!(!active_item.is_singleton(cx));
17356        })
17357        .unwrap();
17358}
17359
17360#[gpui::test]
17361async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17362    init_test(cx, |_| {});
17363
17364    let mut cx = EditorTestContext::new(cx).await;
17365
17366    let diff_base = r#"
17367        use some::mod;
17368
17369        const A: u32 = 42;
17370
17371        fn main() {
17372            println!("hello");
17373
17374            println!("world");
17375        }
17376        "#
17377    .unindent();
17378
17379    cx.set_state(
17380        &r#"
17381        use some::modified;
17382
17383        ˇ
17384        fn main() {
17385            println!("hello there");
17386
17387            println!("around the");
17388            println!("world");
17389        }
17390        "#
17391        .unindent(),
17392    );
17393
17394    cx.set_head_text(&diff_base);
17395    executor.run_until_parked();
17396
17397    cx.update_editor(|editor, window, cx| {
17398        editor.go_to_next_hunk(&GoToHunk, window, cx);
17399        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17400    });
17401    executor.run_until_parked();
17402    cx.assert_state_with_diff(
17403        r#"
17404          use some::modified;
17405
17406
17407          fn main() {
17408        -     println!("hello");
17409        + ˇ    println!("hello there");
17410
17411              println!("around the");
17412              println!("world");
17413          }
17414        "#
17415        .unindent(),
17416    );
17417
17418    cx.update_editor(|editor, window, cx| {
17419        for _ in 0..2 {
17420            editor.go_to_next_hunk(&GoToHunk, window, cx);
17421            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17422        }
17423    });
17424    executor.run_until_parked();
17425    cx.assert_state_with_diff(
17426        r#"
17427        - use some::mod;
17428        + ˇuse some::modified;
17429
17430
17431          fn main() {
17432        -     println!("hello");
17433        +     println!("hello there");
17434
17435        +     println!("around the");
17436              println!("world");
17437          }
17438        "#
17439        .unindent(),
17440    );
17441
17442    cx.update_editor(|editor, window, cx| {
17443        editor.go_to_next_hunk(&GoToHunk, window, cx);
17444        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17445    });
17446    executor.run_until_parked();
17447    cx.assert_state_with_diff(
17448        r#"
17449        - use some::mod;
17450        + use some::modified;
17451
17452        - const A: u32 = 42;
17453          ˇ
17454          fn main() {
17455        -     println!("hello");
17456        +     println!("hello there");
17457
17458        +     println!("around the");
17459              println!("world");
17460          }
17461        "#
17462        .unindent(),
17463    );
17464
17465    cx.update_editor(|editor, window, cx| {
17466        editor.cancel(&Cancel, window, cx);
17467    });
17468
17469    cx.assert_state_with_diff(
17470        r#"
17471          use some::modified;
17472
17473          ˇ
17474          fn main() {
17475              println!("hello there");
17476
17477              println!("around the");
17478              println!("world");
17479          }
17480        "#
17481        .unindent(),
17482    );
17483}
17484
17485#[gpui::test]
17486async fn test_diff_base_change_with_expanded_diff_hunks(
17487    executor: BackgroundExecutor,
17488    cx: &mut TestAppContext,
17489) {
17490    init_test(cx, |_| {});
17491
17492    let mut cx = EditorTestContext::new(cx).await;
17493
17494    let diff_base = r#"
17495        use some::mod1;
17496        use some::mod2;
17497
17498        const A: u32 = 42;
17499        const B: u32 = 42;
17500        const C: u32 = 42;
17501
17502        fn main() {
17503            println!("hello");
17504
17505            println!("world");
17506        }
17507        "#
17508    .unindent();
17509
17510    cx.set_state(
17511        &r#"
17512        use some::mod2;
17513
17514        const A: u32 = 42;
17515        const C: u32 = 42;
17516
17517        fn main(ˇ) {
17518            //println!("hello");
17519
17520            println!("world");
17521            //
17522            //
17523        }
17524        "#
17525        .unindent(),
17526    );
17527
17528    cx.set_head_text(&diff_base);
17529    executor.run_until_parked();
17530
17531    cx.update_editor(|editor, window, cx| {
17532        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17533    });
17534    executor.run_until_parked();
17535    cx.assert_state_with_diff(
17536        r#"
17537        - use some::mod1;
17538          use some::mod2;
17539
17540          const A: u32 = 42;
17541        - const B: u32 = 42;
17542          const C: u32 = 42;
17543
17544          fn main(ˇ) {
17545        -     println!("hello");
17546        +     //println!("hello");
17547
17548              println!("world");
17549        +     //
17550        +     //
17551          }
17552        "#
17553        .unindent(),
17554    );
17555
17556    cx.set_head_text("new diff base!");
17557    executor.run_until_parked();
17558    cx.assert_state_with_diff(
17559        r#"
17560        - new diff base!
17561        + use some::mod2;
17562        +
17563        + const A: u32 = 42;
17564        + const C: u32 = 42;
17565        +
17566        + fn main(ˇ) {
17567        +     //println!("hello");
17568        +
17569        +     println!("world");
17570        +     //
17571        +     //
17572        + }
17573        "#
17574        .unindent(),
17575    );
17576}
17577
17578#[gpui::test]
17579async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17580    init_test(cx, |_| {});
17581
17582    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17583    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17584    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17585    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17586    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17587    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17588
17589    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17590    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17591    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17592
17593    let multi_buffer = cx.new(|cx| {
17594        let mut multibuffer = MultiBuffer::new(ReadWrite);
17595        multibuffer.push_excerpts(
17596            buffer_1.clone(),
17597            [
17598                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17599                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17600                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17601            ],
17602            cx,
17603        );
17604        multibuffer.push_excerpts(
17605            buffer_2.clone(),
17606            [
17607                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17608                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17609                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17610            ],
17611            cx,
17612        );
17613        multibuffer.push_excerpts(
17614            buffer_3.clone(),
17615            [
17616                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17617                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17618                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17619            ],
17620            cx,
17621        );
17622        multibuffer
17623    });
17624
17625    let editor =
17626        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17627    editor
17628        .update(cx, |editor, _window, cx| {
17629            for (buffer, diff_base) in [
17630                (buffer_1.clone(), file_1_old),
17631                (buffer_2.clone(), file_2_old),
17632                (buffer_3.clone(), file_3_old),
17633            ] {
17634                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17635                editor
17636                    .buffer
17637                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17638            }
17639        })
17640        .unwrap();
17641
17642    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17643    cx.run_until_parked();
17644
17645    cx.assert_editor_state(
17646        &"
17647            ˇaaa
17648            ccc
17649            ddd
17650
17651            ggg
17652            hhh
17653
17654
17655            lll
17656            mmm
17657            NNN
17658
17659            qqq
17660            rrr
17661
17662            uuu
17663            111
17664            222
17665            333
17666
17667            666
17668            777
17669
17670            000
17671            !!!"
17672        .unindent(),
17673    );
17674
17675    cx.update_editor(|editor, window, cx| {
17676        editor.select_all(&SelectAll, window, cx);
17677        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17678    });
17679    cx.executor().run_until_parked();
17680
17681    cx.assert_state_with_diff(
17682        "
17683            «aaa
17684          - bbb
17685            ccc
17686            ddd
17687
17688            ggg
17689            hhh
17690
17691
17692            lll
17693            mmm
17694          - nnn
17695          + NNN
17696
17697            qqq
17698            rrr
17699
17700            uuu
17701            111
17702            222
17703            333
17704
17705          + 666
17706            777
17707
17708            000
17709            !!!ˇ»"
17710            .unindent(),
17711    );
17712}
17713
17714#[gpui::test]
17715async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17716    init_test(cx, |_| {});
17717
17718    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17719    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17720
17721    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17722    let multi_buffer = cx.new(|cx| {
17723        let mut multibuffer = MultiBuffer::new(ReadWrite);
17724        multibuffer.push_excerpts(
17725            buffer.clone(),
17726            [
17727                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17728                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17729                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17730            ],
17731            cx,
17732        );
17733        multibuffer
17734    });
17735
17736    let editor =
17737        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17738    editor
17739        .update(cx, |editor, _window, cx| {
17740            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17741            editor
17742                .buffer
17743                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17744        })
17745        .unwrap();
17746
17747    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17748    cx.run_until_parked();
17749
17750    cx.update_editor(|editor, window, cx| {
17751        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17752    });
17753    cx.executor().run_until_parked();
17754
17755    // When the start of a hunk coincides with the start of its excerpt,
17756    // the hunk is expanded. When the start of a a hunk is earlier than
17757    // the start of its excerpt, the hunk is not expanded.
17758    cx.assert_state_with_diff(
17759        "
17760            ˇaaa
17761          - bbb
17762          + BBB
17763
17764          - ddd
17765          - eee
17766          + DDD
17767          + EEE
17768            fff
17769
17770            iii
17771        "
17772        .unindent(),
17773    );
17774}
17775
17776#[gpui::test]
17777async fn test_edits_around_expanded_insertion_hunks(
17778    executor: BackgroundExecutor,
17779    cx: &mut TestAppContext,
17780) {
17781    init_test(cx, |_| {});
17782
17783    let mut cx = EditorTestContext::new(cx).await;
17784
17785    let diff_base = r#"
17786        use some::mod1;
17787        use some::mod2;
17788
17789        const A: u32 = 42;
17790
17791        fn main() {
17792            println!("hello");
17793
17794            println!("world");
17795        }
17796        "#
17797    .unindent();
17798    executor.run_until_parked();
17799    cx.set_state(
17800        &r#"
17801        use some::mod1;
17802        use some::mod2;
17803
17804        const A: u32 = 42;
17805        const B: u32 = 42;
17806        const C: u32 = 42;
17807        ˇ
17808
17809        fn main() {
17810            println!("hello");
17811
17812            println!("world");
17813        }
17814        "#
17815        .unindent(),
17816    );
17817
17818    cx.set_head_text(&diff_base);
17819    executor.run_until_parked();
17820
17821    cx.update_editor(|editor, window, cx| {
17822        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17823    });
17824    executor.run_until_parked();
17825
17826    cx.assert_state_with_diff(
17827        r#"
17828        use some::mod1;
17829        use some::mod2;
17830
17831        const A: u32 = 42;
17832      + const B: u32 = 42;
17833      + const C: u32 = 42;
17834      + ˇ
17835
17836        fn main() {
17837            println!("hello");
17838
17839            println!("world");
17840        }
17841      "#
17842        .unindent(),
17843    );
17844
17845    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17846    executor.run_until_parked();
17847
17848    cx.assert_state_with_diff(
17849        r#"
17850        use some::mod1;
17851        use some::mod2;
17852
17853        const A: u32 = 42;
17854      + const B: u32 = 42;
17855      + const C: u32 = 42;
17856      + const D: u32 = 42;
17857      + ˇ
17858
17859        fn main() {
17860            println!("hello");
17861
17862            println!("world");
17863        }
17864      "#
17865        .unindent(),
17866    );
17867
17868    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17869    executor.run_until_parked();
17870
17871    cx.assert_state_with_diff(
17872        r#"
17873        use some::mod1;
17874        use some::mod2;
17875
17876        const A: u32 = 42;
17877      + const B: u32 = 42;
17878      + const C: u32 = 42;
17879      + const D: u32 = 42;
17880      + const E: u32 = 42;
17881      + ˇ
17882
17883        fn main() {
17884            println!("hello");
17885
17886            println!("world");
17887        }
17888      "#
17889        .unindent(),
17890    );
17891
17892    cx.update_editor(|editor, window, cx| {
17893        editor.delete_line(&DeleteLine, window, cx);
17894    });
17895    executor.run_until_parked();
17896
17897    cx.assert_state_with_diff(
17898        r#"
17899        use some::mod1;
17900        use some::mod2;
17901
17902        const A: u32 = 42;
17903      + const B: u32 = 42;
17904      + const C: u32 = 42;
17905      + const D: u32 = 42;
17906      + const E: u32 = 42;
17907        ˇ
17908        fn main() {
17909            println!("hello");
17910
17911            println!("world");
17912        }
17913      "#
17914        .unindent(),
17915    );
17916
17917    cx.update_editor(|editor, window, cx| {
17918        editor.move_up(&MoveUp, window, cx);
17919        editor.delete_line(&DeleteLine, window, cx);
17920        editor.move_up(&MoveUp, window, cx);
17921        editor.delete_line(&DeleteLine, window, cx);
17922        editor.move_up(&MoveUp, window, cx);
17923        editor.delete_line(&DeleteLine, window, cx);
17924    });
17925    executor.run_until_parked();
17926    cx.assert_state_with_diff(
17927        r#"
17928        use some::mod1;
17929        use some::mod2;
17930
17931        const A: u32 = 42;
17932      + const B: u32 = 42;
17933        ˇ
17934        fn main() {
17935            println!("hello");
17936
17937            println!("world");
17938        }
17939      "#
17940        .unindent(),
17941    );
17942
17943    cx.update_editor(|editor, window, cx| {
17944        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17945        editor.delete_line(&DeleteLine, window, cx);
17946    });
17947    executor.run_until_parked();
17948    cx.assert_state_with_diff(
17949        r#"
17950        ˇ
17951        fn main() {
17952            println!("hello");
17953
17954            println!("world");
17955        }
17956      "#
17957        .unindent(),
17958    );
17959}
17960
17961#[gpui::test]
17962async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17963    init_test(cx, |_| {});
17964
17965    let mut cx = EditorTestContext::new(cx).await;
17966    cx.set_head_text(indoc! { "
17967        one
17968        two
17969        three
17970        four
17971        five
17972        "
17973    });
17974    cx.set_state(indoc! { "
17975        one
17976        ˇthree
17977        five
17978    "});
17979    cx.run_until_parked();
17980    cx.update_editor(|editor, window, cx| {
17981        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17982    });
17983    cx.assert_state_with_diff(
17984        indoc! { "
17985        one
17986      - two
17987        ˇthree
17988      - four
17989        five
17990    "}
17991        .to_string(),
17992    );
17993    cx.update_editor(|editor, window, cx| {
17994        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17995    });
17996
17997    cx.assert_state_with_diff(
17998        indoc! { "
17999        one
18000        ˇthree
18001        five
18002    "}
18003        .to_string(),
18004    );
18005
18006    cx.set_state(indoc! { "
18007        one
18008        ˇTWO
18009        three
18010        four
18011        five
18012    "});
18013    cx.run_until_parked();
18014    cx.update_editor(|editor, window, cx| {
18015        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18016    });
18017
18018    cx.assert_state_with_diff(
18019        indoc! { "
18020            one
18021          - two
18022          + ˇTWO
18023            three
18024            four
18025            five
18026        "}
18027        .to_string(),
18028    );
18029    cx.update_editor(|editor, window, cx| {
18030        editor.move_up(&Default::default(), window, cx);
18031        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18032    });
18033    cx.assert_state_with_diff(
18034        indoc! { "
18035            one
18036            ˇTWO
18037            three
18038            four
18039            five
18040        "}
18041        .to_string(),
18042    );
18043}
18044
18045#[gpui::test]
18046async fn test_edits_around_expanded_deletion_hunks(
18047    executor: BackgroundExecutor,
18048    cx: &mut TestAppContext,
18049) {
18050    init_test(cx, |_| {});
18051
18052    let mut cx = EditorTestContext::new(cx).await;
18053
18054    let diff_base = r#"
18055        use some::mod1;
18056        use some::mod2;
18057
18058        const A: u32 = 42;
18059        const B: u32 = 42;
18060        const C: u32 = 42;
18061
18062
18063        fn main() {
18064            println!("hello");
18065
18066            println!("world");
18067        }
18068    "#
18069    .unindent();
18070    executor.run_until_parked();
18071    cx.set_state(
18072        &r#"
18073        use some::mod1;
18074        use some::mod2;
18075
18076        ˇconst B: u32 = 42;
18077        const C: u32 = 42;
18078
18079
18080        fn main() {
18081            println!("hello");
18082
18083            println!("world");
18084        }
18085        "#
18086        .unindent(),
18087    );
18088
18089    cx.set_head_text(&diff_base);
18090    executor.run_until_parked();
18091
18092    cx.update_editor(|editor, window, cx| {
18093        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18094    });
18095    executor.run_until_parked();
18096
18097    cx.assert_state_with_diff(
18098        r#"
18099        use some::mod1;
18100        use some::mod2;
18101
18102      - const A: u32 = 42;
18103        ˇconst B: u32 = 42;
18104        const C: u32 = 42;
18105
18106
18107        fn main() {
18108            println!("hello");
18109
18110            println!("world");
18111        }
18112      "#
18113        .unindent(),
18114    );
18115
18116    cx.update_editor(|editor, window, cx| {
18117        editor.delete_line(&DeleteLine, window, cx);
18118    });
18119    executor.run_until_parked();
18120    cx.assert_state_with_diff(
18121        r#"
18122        use some::mod1;
18123        use some::mod2;
18124
18125      - const A: u32 = 42;
18126      - const B: u32 = 42;
18127        ˇconst C: u32 = 42;
18128
18129
18130        fn main() {
18131            println!("hello");
18132
18133            println!("world");
18134        }
18135      "#
18136        .unindent(),
18137    );
18138
18139    cx.update_editor(|editor, window, cx| {
18140        editor.delete_line(&DeleteLine, window, cx);
18141    });
18142    executor.run_until_parked();
18143    cx.assert_state_with_diff(
18144        r#"
18145        use some::mod1;
18146        use some::mod2;
18147
18148      - const A: u32 = 42;
18149      - const B: u32 = 42;
18150      - const C: u32 = 42;
18151        ˇ
18152
18153        fn main() {
18154            println!("hello");
18155
18156            println!("world");
18157        }
18158      "#
18159        .unindent(),
18160    );
18161
18162    cx.update_editor(|editor, window, cx| {
18163        editor.handle_input("replacement", window, cx);
18164    });
18165    executor.run_until_parked();
18166    cx.assert_state_with_diff(
18167        r#"
18168        use some::mod1;
18169        use some::mod2;
18170
18171      - const A: u32 = 42;
18172      - const B: u32 = 42;
18173      - const C: u32 = 42;
18174      -
18175      + replacementˇ
18176
18177        fn main() {
18178            println!("hello");
18179
18180            println!("world");
18181        }
18182      "#
18183        .unindent(),
18184    );
18185}
18186
18187#[gpui::test]
18188async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18189    init_test(cx, |_| {});
18190
18191    let mut cx = EditorTestContext::new(cx).await;
18192
18193    let base_text = r#"
18194        one
18195        two
18196        three
18197        four
18198        five
18199    "#
18200    .unindent();
18201    executor.run_until_parked();
18202    cx.set_state(
18203        &r#"
18204        one
18205        two
18206        fˇour
18207        five
18208        "#
18209        .unindent(),
18210    );
18211
18212    cx.set_head_text(&base_text);
18213    executor.run_until_parked();
18214
18215    cx.update_editor(|editor, window, cx| {
18216        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18217    });
18218    executor.run_until_parked();
18219
18220    cx.assert_state_with_diff(
18221        r#"
18222          one
18223          two
18224        - three
18225          fˇour
18226          five
18227        "#
18228        .unindent(),
18229    );
18230
18231    cx.update_editor(|editor, window, cx| {
18232        editor.backspace(&Backspace, window, cx);
18233        editor.backspace(&Backspace, window, cx);
18234    });
18235    executor.run_until_parked();
18236    cx.assert_state_with_diff(
18237        r#"
18238          one
18239          two
18240        - threeˇ
18241        - four
18242        + our
18243          five
18244        "#
18245        .unindent(),
18246    );
18247}
18248
18249#[gpui::test]
18250async fn test_edit_after_expanded_modification_hunk(
18251    executor: BackgroundExecutor,
18252    cx: &mut TestAppContext,
18253) {
18254    init_test(cx, |_| {});
18255
18256    let mut cx = EditorTestContext::new(cx).await;
18257
18258    let diff_base = r#"
18259        use some::mod1;
18260        use some::mod2;
18261
18262        const A: u32 = 42;
18263        const B: u32 = 42;
18264        const C: u32 = 42;
18265        const D: u32 = 42;
18266
18267
18268        fn main() {
18269            println!("hello");
18270
18271            println!("world");
18272        }"#
18273    .unindent();
18274
18275    cx.set_state(
18276        &r#"
18277        use some::mod1;
18278        use some::mod2;
18279
18280        const A: u32 = 42;
18281        const B: u32 = 42;
18282        const C: u32 = 43ˇ
18283        const D: u32 = 42;
18284
18285
18286        fn main() {
18287            println!("hello");
18288
18289            println!("world");
18290        }"#
18291        .unindent(),
18292    );
18293
18294    cx.set_head_text(&diff_base);
18295    executor.run_until_parked();
18296    cx.update_editor(|editor, window, cx| {
18297        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18298    });
18299    executor.run_until_parked();
18300
18301    cx.assert_state_with_diff(
18302        r#"
18303        use some::mod1;
18304        use some::mod2;
18305
18306        const A: u32 = 42;
18307        const B: u32 = 42;
18308      - const C: u32 = 42;
18309      + const C: u32 = 43ˇ
18310        const D: u32 = 42;
18311
18312
18313        fn main() {
18314            println!("hello");
18315
18316            println!("world");
18317        }"#
18318        .unindent(),
18319    );
18320
18321    cx.update_editor(|editor, window, cx| {
18322        editor.handle_input("\nnew_line\n", window, cx);
18323    });
18324    executor.run_until_parked();
18325
18326    cx.assert_state_with_diff(
18327        r#"
18328        use some::mod1;
18329        use some::mod2;
18330
18331        const A: u32 = 42;
18332        const B: u32 = 42;
18333      - const C: u32 = 42;
18334      + const C: u32 = 43
18335      + new_line
18336      + ˇ
18337        const D: u32 = 42;
18338
18339
18340        fn main() {
18341            println!("hello");
18342
18343            println!("world");
18344        }"#
18345        .unindent(),
18346    );
18347}
18348
18349#[gpui::test]
18350async fn test_stage_and_unstage_added_file_hunk(
18351    executor: BackgroundExecutor,
18352    cx: &mut TestAppContext,
18353) {
18354    init_test(cx, |_| {});
18355
18356    let mut cx = EditorTestContext::new(cx).await;
18357    cx.update_editor(|editor, _, cx| {
18358        editor.set_expand_all_diff_hunks(cx);
18359    });
18360
18361    let working_copy = r#"
18362            ˇfn main() {
18363                println!("hello, world!");
18364            }
18365        "#
18366    .unindent();
18367
18368    cx.set_state(&working_copy);
18369    executor.run_until_parked();
18370
18371    cx.assert_state_with_diff(
18372        r#"
18373            + ˇfn main() {
18374            +     println!("hello, world!");
18375            + }
18376        "#
18377        .unindent(),
18378    );
18379    cx.assert_index_text(None);
18380
18381    cx.update_editor(|editor, window, cx| {
18382        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18383    });
18384    executor.run_until_parked();
18385    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18386    cx.assert_state_with_diff(
18387        r#"
18388            + ˇfn main() {
18389            +     println!("hello, world!");
18390            + }
18391        "#
18392        .unindent(),
18393    );
18394
18395    cx.update_editor(|editor, window, cx| {
18396        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18397    });
18398    executor.run_until_parked();
18399    cx.assert_index_text(None);
18400}
18401
18402async fn setup_indent_guides_editor(
18403    text: &str,
18404    cx: &mut TestAppContext,
18405) -> (BufferId, EditorTestContext) {
18406    init_test(cx, |_| {});
18407
18408    let mut cx = EditorTestContext::new(cx).await;
18409
18410    let buffer_id = cx.update_editor(|editor, window, cx| {
18411        editor.set_text(text, window, cx);
18412        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18413
18414        buffer_ids[0]
18415    });
18416
18417    (buffer_id, cx)
18418}
18419
18420fn assert_indent_guides(
18421    range: Range<u32>,
18422    expected: Vec<IndentGuide>,
18423    active_indices: Option<Vec<usize>>,
18424    cx: &mut EditorTestContext,
18425) {
18426    let indent_guides = cx.update_editor(|editor, window, cx| {
18427        let snapshot = editor.snapshot(window, cx).display_snapshot;
18428        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18429            editor,
18430            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18431            true,
18432            &snapshot,
18433            cx,
18434        );
18435
18436        indent_guides.sort_by(|a, b| {
18437            a.depth.cmp(&b.depth).then(
18438                a.start_row
18439                    .cmp(&b.start_row)
18440                    .then(a.end_row.cmp(&b.end_row)),
18441            )
18442        });
18443        indent_guides
18444    });
18445
18446    if let Some(expected) = active_indices {
18447        let active_indices = cx.update_editor(|editor, window, cx| {
18448            let snapshot = editor.snapshot(window, cx).display_snapshot;
18449            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18450        });
18451
18452        assert_eq!(
18453            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18454            expected,
18455            "Active indent guide indices do not match"
18456        );
18457    }
18458
18459    assert_eq!(indent_guides, expected, "Indent guides do not match");
18460}
18461
18462fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18463    IndentGuide {
18464        buffer_id,
18465        start_row: MultiBufferRow(start_row),
18466        end_row: MultiBufferRow(end_row),
18467        depth,
18468        tab_size: 4,
18469        settings: IndentGuideSettings {
18470            enabled: true,
18471            line_width: 1,
18472            active_line_width: 1,
18473            ..Default::default()
18474        },
18475    }
18476}
18477
18478#[gpui::test]
18479async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18480    let (buffer_id, mut cx) = setup_indent_guides_editor(
18481        &"
18482        fn main() {
18483            let a = 1;
18484        }"
18485        .unindent(),
18486        cx,
18487    )
18488    .await;
18489
18490    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18491}
18492
18493#[gpui::test]
18494async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18495    let (buffer_id, mut cx) = setup_indent_guides_editor(
18496        &"
18497        fn main() {
18498            let a = 1;
18499            let b = 2;
18500        }"
18501        .unindent(),
18502        cx,
18503    )
18504    .await;
18505
18506    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18507}
18508
18509#[gpui::test]
18510async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18511    let (buffer_id, mut cx) = setup_indent_guides_editor(
18512        &"
18513        fn main() {
18514            let a = 1;
18515            if a == 3 {
18516                let b = 2;
18517            } else {
18518                let c = 3;
18519            }
18520        }"
18521        .unindent(),
18522        cx,
18523    )
18524    .await;
18525
18526    assert_indent_guides(
18527        0..8,
18528        vec![
18529            indent_guide(buffer_id, 1, 6, 0),
18530            indent_guide(buffer_id, 3, 3, 1),
18531            indent_guide(buffer_id, 5, 5, 1),
18532        ],
18533        None,
18534        &mut cx,
18535    );
18536}
18537
18538#[gpui::test]
18539async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18540    let (buffer_id, mut cx) = setup_indent_guides_editor(
18541        &"
18542        fn main() {
18543            let a = 1;
18544                let b = 2;
18545            let c = 3;
18546        }"
18547        .unindent(),
18548        cx,
18549    )
18550    .await;
18551
18552    assert_indent_guides(
18553        0..5,
18554        vec![
18555            indent_guide(buffer_id, 1, 3, 0),
18556            indent_guide(buffer_id, 2, 2, 1),
18557        ],
18558        None,
18559        &mut cx,
18560    );
18561}
18562
18563#[gpui::test]
18564async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18565    let (buffer_id, mut cx) = setup_indent_guides_editor(
18566        &"
18567        fn main() {
18568            let a = 1;
18569
18570            let c = 3;
18571        }"
18572        .unindent(),
18573        cx,
18574    )
18575    .await;
18576
18577    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18578}
18579
18580#[gpui::test]
18581async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18582    let (buffer_id, mut cx) = setup_indent_guides_editor(
18583        &"
18584        fn main() {
18585            let a = 1;
18586
18587            let c = 3;
18588
18589            if a == 3 {
18590                let b = 2;
18591            } else {
18592                let c = 3;
18593            }
18594        }"
18595        .unindent(),
18596        cx,
18597    )
18598    .await;
18599
18600    assert_indent_guides(
18601        0..11,
18602        vec![
18603            indent_guide(buffer_id, 1, 9, 0),
18604            indent_guide(buffer_id, 6, 6, 1),
18605            indent_guide(buffer_id, 8, 8, 1),
18606        ],
18607        None,
18608        &mut cx,
18609    );
18610}
18611
18612#[gpui::test]
18613async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18614    let (buffer_id, mut cx) = setup_indent_guides_editor(
18615        &"
18616        fn main() {
18617            let a = 1;
18618
18619            let c = 3;
18620
18621            if a == 3 {
18622                let b = 2;
18623            } else {
18624                let c = 3;
18625            }
18626        }"
18627        .unindent(),
18628        cx,
18629    )
18630    .await;
18631
18632    assert_indent_guides(
18633        1..11,
18634        vec![
18635            indent_guide(buffer_id, 1, 9, 0),
18636            indent_guide(buffer_id, 6, 6, 1),
18637            indent_guide(buffer_id, 8, 8, 1),
18638        ],
18639        None,
18640        &mut cx,
18641    );
18642}
18643
18644#[gpui::test]
18645async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18646    let (buffer_id, mut cx) = setup_indent_guides_editor(
18647        &"
18648        fn main() {
18649            let a = 1;
18650
18651            let c = 3;
18652
18653            if a == 3 {
18654                let b = 2;
18655            } else {
18656                let c = 3;
18657            }
18658        }"
18659        .unindent(),
18660        cx,
18661    )
18662    .await;
18663
18664    assert_indent_guides(
18665        1..10,
18666        vec![
18667            indent_guide(buffer_id, 1, 9, 0),
18668            indent_guide(buffer_id, 6, 6, 1),
18669            indent_guide(buffer_id, 8, 8, 1),
18670        ],
18671        None,
18672        &mut cx,
18673    );
18674}
18675
18676#[gpui::test]
18677async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18678    let (buffer_id, mut cx) = setup_indent_guides_editor(
18679        &"
18680        fn main() {
18681            if a {
18682                b(
18683                    c,
18684                    d,
18685                )
18686            } else {
18687                e(
18688                    f
18689                )
18690            }
18691        }"
18692        .unindent(),
18693        cx,
18694    )
18695    .await;
18696
18697    assert_indent_guides(
18698        0..11,
18699        vec![
18700            indent_guide(buffer_id, 1, 10, 0),
18701            indent_guide(buffer_id, 2, 5, 1),
18702            indent_guide(buffer_id, 7, 9, 1),
18703            indent_guide(buffer_id, 3, 4, 2),
18704            indent_guide(buffer_id, 8, 8, 2),
18705        ],
18706        None,
18707        &mut cx,
18708    );
18709
18710    cx.update_editor(|editor, window, cx| {
18711        editor.fold_at(MultiBufferRow(2), window, cx);
18712        assert_eq!(
18713            editor.display_text(cx),
18714            "
18715            fn main() {
18716                if a {
18717                    b(⋯
18718                    )
18719                } else {
18720                    e(
18721                        f
18722                    )
18723                }
18724            }"
18725            .unindent()
18726        );
18727    });
18728
18729    assert_indent_guides(
18730        0..11,
18731        vec![
18732            indent_guide(buffer_id, 1, 10, 0),
18733            indent_guide(buffer_id, 2, 5, 1),
18734            indent_guide(buffer_id, 7, 9, 1),
18735            indent_guide(buffer_id, 8, 8, 2),
18736        ],
18737        None,
18738        &mut cx,
18739    );
18740}
18741
18742#[gpui::test]
18743async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18744    let (buffer_id, mut cx) = setup_indent_guides_editor(
18745        &"
18746        block1
18747            block2
18748                block3
18749                    block4
18750            block2
18751        block1
18752        block1"
18753            .unindent(),
18754        cx,
18755    )
18756    .await;
18757
18758    assert_indent_guides(
18759        1..10,
18760        vec![
18761            indent_guide(buffer_id, 1, 4, 0),
18762            indent_guide(buffer_id, 2, 3, 1),
18763            indent_guide(buffer_id, 3, 3, 2),
18764        ],
18765        None,
18766        &mut cx,
18767    );
18768}
18769
18770#[gpui::test]
18771async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18772    let (buffer_id, mut cx) = setup_indent_guides_editor(
18773        &"
18774        block1
18775            block2
18776                block3
18777
18778        block1
18779        block1"
18780            .unindent(),
18781        cx,
18782    )
18783    .await;
18784
18785    assert_indent_guides(
18786        0..6,
18787        vec![
18788            indent_guide(buffer_id, 1, 2, 0),
18789            indent_guide(buffer_id, 2, 2, 1),
18790        ],
18791        None,
18792        &mut cx,
18793    );
18794}
18795
18796#[gpui::test]
18797async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18798    let (buffer_id, mut cx) = setup_indent_guides_editor(
18799        &"
18800        function component() {
18801        \treturn (
18802        \t\t\t
18803        \t\t<div>
18804        \t\t\t<abc></abc>
18805        \t\t</div>
18806        \t)
18807        }"
18808        .unindent(),
18809        cx,
18810    )
18811    .await;
18812
18813    assert_indent_guides(
18814        0..8,
18815        vec![
18816            indent_guide(buffer_id, 1, 6, 0),
18817            indent_guide(buffer_id, 2, 5, 1),
18818            indent_guide(buffer_id, 4, 4, 2),
18819        ],
18820        None,
18821        &mut cx,
18822    );
18823}
18824
18825#[gpui::test]
18826async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18827    let (buffer_id, mut cx) = setup_indent_guides_editor(
18828        &"
18829        function component() {
18830        \treturn (
18831        \t
18832        \t\t<div>
18833        \t\t\t<abc></abc>
18834        \t\t</div>
18835        \t)
18836        }"
18837        .unindent(),
18838        cx,
18839    )
18840    .await;
18841
18842    assert_indent_guides(
18843        0..8,
18844        vec![
18845            indent_guide(buffer_id, 1, 6, 0),
18846            indent_guide(buffer_id, 2, 5, 1),
18847            indent_guide(buffer_id, 4, 4, 2),
18848        ],
18849        None,
18850        &mut cx,
18851    );
18852}
18853
18854#[gpui::test]
18855async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18856    let (buffer_id, mut cx) = setup_indent_guides_editor(
18857        &"
18858        block1
18859
18860
18861
18862            block2
18863        "
18864        .unindent(),
18865        cx,
18866    )
18867    .await;
18868
18869    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18870}
18871
18872#[gpui::test]
18873async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18874    let (buffer_id, mut cx) = setup_indent_guides_editor(
18875        &"
18876        def a:
18877        \tb = 3
18878        \tif True:
18879        \t\tc = 4
18880        \t\td = 5
18881        \tprint(b)
18882        "
18883        .unindent(),
18884        cx,
18885    )
18886    .await;
18887
18888    assert_indent_guides(
18889        0..6,
18890        vec![
18891            indent_guide(buffer_id, 1, 5, 0),
18892            indent_guide(buffer_id, 3, 4, 1),
18893        ],
18894        None,
18895        &mut cx,
18896    );
18897}
18898
18899#[gpui::test]
18900async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18901    let (buffer_id, mut cx) = setup_indent_guides_editor(
18902        &"
18903    fn main() {
18904        let a = 1;
18905    }"
18906        .unindent(),
18907        cx,
18908    )
18909    .await;
18910
18911    cx.update_editor(|editor, window, cx| {
18912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18913            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18914        });
18915    });
18916
18917    assert_indent_guides(
18918        0..3,
18919        vec![indent_guide(buffer_id, 1, 1, 0)],
18920        Some(vec![0]),
18921        &mut cx,
18922    );
18923}
18924
18925#[gpui::test]
18926async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18927    let (buffer_id, mut cx) = setup_indent_guides_editor(
18928        &"
18929    fn main() {
18930        if 1 == 2 {
18931            let a = 1;
18932        }
18933    }"
18934        .unindent(),
18935        cx,
18936    )
18937    .await;
18938
18939    cx.update_editor(|editor, window, cx| {
18940        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18941            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18942        });
18943    });
18944
18945    assert_indent_guides(
18946        0..4,
18947        vec![
18948            indent_guide(buffer_id, 1, 3, 0),
18949            indent_guide(buffer_id, 2, 2, 1),
18950        ],
18951        Some(vec![1]),
18952        &mut cx,
18953    );
18954
18955    cx.update_editor(|editor, window, cx| {
18956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18957            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18958        });
18959    });
18960
18961    assert_indent_guides(
18962        0..4,
18963        vec![
18964            indent_guide(buffer_id, 1, 3, 0),
18965            indent_guide(buffer_id, 2, 2, 1),
18966        ],
18967        Some(vec![1]),
18968        &mut cx,
18969    );
18970
18971    cx.update_editor(|editor, window, cx| {
18972        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18973            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18974        });
18975    });
18976
18977    assert_indent_guides(
18978        0..4,
18979        vec![
18980            indent_guide(buffer_id, 1, 3, 0),
18981            indent_guide(buffer_id, 2, 2, 1),
18982        ],
18983        Some(vec![0]),
18984        &mut cx,
18985    );
18986}
18987
18988#[gpui::test]
18989async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18990    let (buffer_id, mut cx) = setup_indent_guides_editor(
18991        &"
18992    fn main() {
18993        let a = 1;
18994
18995        let b = 2;
18996    }"
18997        .unindent(),
18998        cx,
18999    )
19000    .await;
19001
19002    cx.update_editor(|editor, window, cx| {
19003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19004            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19005        });
19006    });
19007
19008    assert_indent_guides(
19009        0..5,
19010        vec![indent_guide(buffer_id, 1, 3, 0)],
19011        Some(vec![0]),
19012        &mut cx,
19013    );
19014}
19015
19016#[gpui::test]
19017async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19018    let (buffer_id, mut cx) = setup_indent_guides_editor(
19019        &"
19020    def m:
19021        a = 1
19022        pass"
19023            .unindent(),
19024        cx,
19025    )
19026    .await;
19027
19028    cx.update_editor(|editor, window, cx| {
19029        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19030            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19031        });
19032    });
19033
19034    assert_indent_guides(
19035        0..3,
19036        vec![indent_guide(buffer_id, 1, 2, 0)],
19037        Some(vec![0]),
19038        &mut cx,
19039    );
19040}
19041
19042#[gpui::test]
19043async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19044    init_test(cx, |_| {});
19045    let mut cx = EditorTestContext::new(cx).await;
19046    let text = indoc! {
19047        "
19048        impl A {
19049            fn b() {
19050                0;
19051                3;
19052                5;
19053                6;
19054                7;
19055            }
19056        }
19057        "
19058    };
19059    let base_text = indoc! {
19060        "
19061        impl A {
19062            fn b() {
19063                0;
19064                1;
19065                2;
19066                3;
19067                4;
19068            }
19069            fn c() {
19070                5;
19071                6;
19072                7;
19073            }
19074        }
19075        "
19076    };
19077
19078    cx.update_editor(|editor, window, cx| {
19079        editor.set_text(text, window, cx);
19080
19081        editor.buffer().update(cx, |multibuffer, cx| {
19082            let buffer = multibuffer.as_singleton().unwrap();
19083            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19084
19085            multibuffer.set_all_diff_hunks_expanded(cx);
19086            multibuffer.add_diff(diff, cx);
19087
19088            buffer.read(cx).remote_id()
19089        })
19090    });
19091    cx.run_until_parked();
19092
19093    cx.assert_state_with_diff(
19094        indoc! { "
19095          impl A {
19096              fn b() {
19097                  0;
19098        -         1;
19099        -         2;
19100                  3;
19101        -         4;
19102        -     }
19103        -     fn c() {
19104                  5;
19105                  6;
19106                  7;
19107              }
19108          }
19109          ˇ"
19110        }
19111        .to_string(),
19112    );
19113
19114    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19115        editor
19116            .snapshot(window, cx)
19117            .buffer_snapshot
19118            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19119            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19120            .collect::<Vec<_>>()
19121    });
19122    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19123    assert_eq!(
19124        actual_guides,
19125        vec![
19126            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19127            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19128            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19129        ]
19130    );
19131}
19132
19133#[gpui::test]
19134async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19135    init_test(cx, |_| {});
19136    let mut cx = EditorTestContext::new(cx).await;
19137
19138    let diff_base = r#"
19139        a
19140        b
19141        c
19142        "#
19143    .unindent();
19144
19145    cx.set_state(
19146        &r#"
19147        ˇA
19148        b
19149        C
19150        "#
19151        .unindent(),
19152    );
19153    cx.set_head_text(&diff_base);
19154    cx.update_editor(|editor, window, cx| {
19155        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19156    });
19157    executor.run_until_parked();
19158
19159    let both_hunks_expanded = r#"
19160        - a
19161        + ˇA
19162          b
19163        - c
19164        + C
19165        "#
19166    .unindent();
19167
19168    cx.assert_state_with_diff(both_hunks_expanded.clone());
19169
19170    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19171        let snapshot = editor.snapshot(window, cx);
19172        let hunks = editor
19173            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19174            .collect::<Vec<_>>();
19175        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19176        let buffer_id = hunks[0].buffer_id;
19177        hunks
19178            .into_iter()
19179            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19180            .collect::<Vec<_>>()
19181    });
19182    assert_eq!(hunk_ranges.len(), 2);
19183
19184    cx.update_editor(|editor, _, cx| {
19185        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19186    });
19187    executor.run_until_parked();
19188
19189    let second_hunk_expanded = r#"
19190          ˇA
19191          b
19192        - c
19193        + C
19194        "#
19195    .unindent();
19196
19197    cx.assert_state_with_diff(second_hunk_expanded);
19198
19199    cx.update_editor(|editor, _, cx| {
19200        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19201    });
19202    executor.run_until_parked();
19203
19204    cx.assert_state_with_diff(both_hunks_expanded.clone());
19205
19206    cx.update_editor(|editor, _, cx| {
19207        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19208    });
19209    executor.run_until_parked();
19210
19211    let first_hunk_expanded = r#"
19212        - a
19213        + ˇA
19214          b
19215          C
19216        "#
19217    .unindent();
19218
19219    cx.assert_state_with_diff(first_hunk_expanded);
19220
19221    cx.update_editor(|editor, _, cx| {
19222        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19223    });
19224    executor.run_until_parked();
19225
19226    cx.assert_state_with_diff(both_hunks_expanded);
19227
19228    cx.set_state(
19229        &r#"
19230        ˇA
19231        b
19232        "#
19233        .unindent(),
19234    );
19235    cx.run_until_parked();
19236
19237    // TODO this cursor position seems bad
19238    cx.assert_state_with_diff(
19239        r#"
19240        - ˇa
19241        + A
19242          b
19243        "#
19244        .unindent(),
19245    );
19246
19247    cx.update_editor(|editor, window, cx| {
19248        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19249    });
19250
19251    cx.assert_state_with_diff(
19252        r#"
19253            - ˇa
19254            + A
19255              b
19256            - c
19257            "#
19258        .unindent(),
19259    );
19260
19261    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19262        let snapshot = editor.snapshot(window, cx);
19263        let hunks = editor
19264            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19265            .collect::<Vec<_>>();
19266        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19267        let buffer_id = hunks[0].buffer_id;
19268        hunks
19269            .into_iter()
19270            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19271            .collect::<Vec<_>>()
19272    });
19273    assert_eq!(hunk_ranges.len(), 2);
19274
19275    cx.update_editor(|editor, _, cx| {
19276        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19277    });
19278    executor.run_until_parked();
19279
19280    cx.assert_state_with_diff(
19281        r#"
19282        - ˇa
19283        + A
19284          b
19285        "#
19286        .unindent(),
19287    );
19288}
19289
19290#[gpui::test]
19291async fn test_toggle_deletion_hunk_at_start_of_file(
19292    executor: BackgroundExecutor,
19293    cx: &mut TestAppContext,
19294) {
19295    init_test(cx, |_| {});
19296    let mut cx = EditorTestContext::new(cx).await;
19297
19298    let diff_base = r#"
19299        a
19300        b
19301        c
19302        "#
19303    .unindent();
19304
19305    cx.set_state(
19306        &r#"
19307        ˇb
19308        c
19309        "#
19310        .unindent(),
19311    );
19312    cx.set_head_text(&diff_base);
19313    cx.update_editor(|editor, window, cx| {
19314        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19315    });
19316    executor.run_until_parked();
19317
19318    let hunk_expanded = r#"
19319        - a
19320          ˇb
19321          c
19322        "#
19323    .unindent();
19324
19325    cx.assert_state_with_diff(hunk_expanded.clone());
19326
19327    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19328        let snapshot = editor.snapshot(window, cx);
19329        let hunks = editor
19330            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19331            .collect::<Vec<_>>();
19332        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19333        let buffer_id = hunks[0].buffer_id;
19334        hunks
19335            .into_iter()
19336            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19337            .collect::<Vec<_>>()
19338    });
19339    assert_eq!(hunk_ranges.len(), 1);
19340
19341    cx.update_editor(|editor, _, cx| {
19342        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19343    });
19344    executor.run_until_parked();
19345
19346    let hunk_collapsed = r#"
19347          ˇb
19348          c
19349        "#
19350    .unindent();
19351
19352    cx.assert_state_with_diff(hunk_collapsed);
19353
19354    cx.update_editor(|editor, _, cx| {
19355        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19356    });
19357    executor.run_until_parked();
19358
19359    cx.assert_state_with_diff(hunk_expanded.clone());
19360}
19361
19362#[gpui::test]
19363async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19364    init_test(cx, |_| {});
19365
19366    let fs = FakeFs::new(cx.executor());
19367    fs.insert_tree(
19368        path!("/test"),
19369        json!({
19370            ".git": {},
19371            "file-1": "ONE\n",
19372            "file-2": "TWO\n",
19373            "file-3": "THREE\n",
19374        }),
19375    )
19376    .await;
19377
19378    fs.set_head_for_repo(
19379        path!("/test/.git").as_ref(),
19380        &[
19381            ("file-1".into(), "one\n".into()),
19382            ("file-2".into(), "two\n".into()),
19383            ("file-3".into(), "three\n".into()),
19384        ],
19385        "deadbeef",
19386    );
19387
19388    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19389    let mut buffers = vec![];
19390    for i in 1..=3 {
19391        let buffer = project
19392            .update(cx, |project, cx| {
19393                let path = format!(path!("/test/file-{}"), i);
19394                project.open_local_buffer(path, cx)
19395            })
19396            .await
19397            .unwrap();
19398        buffers.push(buffer);
19399    }
19400
19401    let multibuffer = cx.new(|cx| {
19402        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19403        multibuffer.set_all_diff_hunks_expanded(cx);
19404        for buffer in &buffers {
19405            let snapshot = buffer.read(cx).snapshot();
19406            multibuffer.set_excerpts_for_path(
19407                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19408                buffer.clone(),
19409                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19410                DEFAULT_MULTIBUFFER_CONTEXT,
19411                cx,
19412            );
19413        }
19414        multibuffer
19415    });
19416
19417    let editor = cx.add_window(|window, cx| {
19418        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19419    });
19420    cx.run_until_parked();
19421
19422    let snapshot = editor
19423        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19424        .unwrap();
19425    let hunks = snapshot
19426        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19427        .map(|hunk| match hunk {
19428            DisplayDiffHunk::Unfolded {
19429                display_row_range, ..
19430            } => display_row_range,
19431            DisplayDiffHunk::Folded { .. } => unreachable!(),
19432        })
19433        .collect::<Vec<_>>();
19434    assert_eq!(
19435        hunks,
19436        [
19437            DisplayRow(2)..DisplayRow(4),
19438            DisplayRow(7)..DisplayRow(9),
19439            DisplayRow(12)..DisplayRow(14),
19440        ]
19441    );
19442}
19443
19444#[gpui::test]
19445async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19446    init_test(cx, |_| {});
19447
19448    let mut cx = EditorTestContext::new(cx).await;
19449    cx.set_head_text(indoc! { "
19450        one
19451        two
19452        three
19453        four
19454        five
19455        "
19456    });
19457    cx.set_index_text(indoc! { "
19458        one
19459        two
19460        three
19461        four
19462        five
19463        "
19464    });
19465    cx.set_state(indoc! {"
19466        one
19467        TWO
19468        ˇTHREE
19469        FOUR
19470        five
19471    "});
19472    cx.run_until_parked();
19473    cx.update_editor(|editor, window, cx| {
19474        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19475    });
19476    cx.run_until_parked();
19477    cx.assert_index_text(Some(indoc! {"
19478        one
19479        TWO
19480        THREE
19481        FOUR
19482        five
19483    "}));
19484    cx.set_state(indoc! { "
19485        one
19486        TWO
19487        ˇTHREE-HUNDRED
19488        FOUR
19489        five
19490    "});
19491    cx.run_until_parked();
19492    cx.update_editor(|editor, window, cx| {
19493        let snapshot = editor.snapshot(window, cx);
19494        let hunks = editor
19495            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19496            .collect::<Vec<_>>();
19497        assert_eq!(hunks.len(), 1);
19498        assert_eq!(
19499            hunks[0].status(),
19500            DiffHunkStatus {
19501                kind: DiffHunkStatusKind::Modified,
19502                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19503            }
19504        );
19505
19506        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19507    });
19508    cx.run_until_parked();
19509    cx.assert_index_text(Some(indoc! {"
19510        one
19511        TWO
19512        THREE-HUNDRED
19513        FOUR
19514        five
19515    "}));
19516}
19517
19518#[gpui::test]
19519fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19520    init_test(cx, |_| {});
19521
19522    let editor = cx.add_window(|window, cx| {
19523        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19524        build_editor(buffer, window, cx)
19525    });
19526
19527    let render_args = Arc::new(Mutex::new(None));
19528    let snapshot = editor
19529        .update(cx, |editor, window, cx| {
19530            let snapshot = editor.buffer().read(cx).snapshot(cx);
19531            let range =
19532                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19533
19534            struct RenderArgs {
19535                row: MultiBufferRow,
19536                folded: bool,
19537                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19538            }
19539
19540            let crease = Crease::inline(
19541                range,
19542                FoldPlaceholder::test(),
19543                {
19544                    let toggle_callback = render_args.clone();
19545                    move |row, folded, callback, _window, _cx| {
19546                        *toggle_callback.lock() = Some(RenderArgs {
19547                            row,
19548                            folded,
19549                            callback,
19550                        });
19551                        div()
19552                    }
19553                },
19554                |_row, _folded, _window, _cx| div(),
19555            );
19556
19557            editor.insert_creases(Some(crease), cx);
19558            let snapshot = editor.snapshot(window, cx);
19559            let _div = snapshot.render_crease_toggle(
19560                MultiBufferRow(1),
19561                false,
19562                cx.entity().clone(),
19563                window,
19564                cx,
19565            );
19566            snapshot
19567        })
19568        .unwrap();
19569
19570    let render_args = render_args.lock().take().unwrap();
19571    assert_eq!(render_args.row, MultiBufferRow(1));
19572    assert!(!render_args.folded);
19573    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19574
19575    cx.update_window(*editor, |_, window, cx| {
19576        (render_args.callback)(true, window, cx)
19577    })
19578    .unwrap();
19579    let snapshot = editor
19580        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19581        .unwrap();
19582    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19583
19584    cx.update_window(*editor, |_, window, cx| {
19585        (render_args.callback)(false, window, cx)
19586    })
19587    .unwrap();
19588    let snapshot = editor
19589        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19590        .unwrap();
19591    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19592}
19593
19594#[gpui::test]
19595async fn test_input_text(cx: &mut TestAppContext) {
19596    init_test(cx, |_| {});
19597    let mut cx = EditorTestContext::new(cx).await;
19598
19599    cx.set_state(
19600        &r#"ˇone
19601        two
19602
19603        three
19604        fourˇ
19605        five
19606
19607        siˇx"#
19608            .unindent(),
19609    );
19610
19611    cx.dispatch_action(HandleInput(String::new()));
19612    cx.assert_editor_state(
19613        &r#"ˇone
19614        two
19615
19616        three
19617        fourˇ
19618        five
19619
19620        siˇx"#
19621            .unindent(),
19622    );
19623
19624    cx.dispatch_action(HandleInput("AAAA".to_string()));
19625    cx.assert_editor_state(
19626        &r#"AAAAˇone
19627        two
19628
19629        three
19630        fourAAAAˇ
19631        five
19632
19633        siAAAAˇx"#
19634            .unindent(),
19635    );
19636}
19637
19638#[gpui::test]
19639async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19640    init_test(cx, |_| {});
19641
19642    let mut cx = EditorTestContext::new(cx).await;
19643    cx.set_state(
19644        r#"let foo = 1;
19645let foo = 2;
19646let foo = 3;
19647let fooˇ = 4;
19648let foo = 5;
19649let foo = 6;
19650let foo = 7;
19651let foo = 8;
19652let foo = 9;
19653let foo = 10;
19654let foo = 11;
19655let foo = 12;
19656let foo = 13;
19657let foo = 14;
19658let foo = 15;"#,
19659    );
19660
19661    cx.update_editor(|e, window, cx| {
19662        assert_eq!(
19663            e.next_scroll_position,
19664            NextScrollCursorCenterTopBottom::Center,
19665            "Default next scroll direction is center",
19666        );
19667
19668        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19669        assert_eq!(
19670            e.next_scroll_position,
19671            NextScrollCursorCenterTopBottom::Top,
19672            "After center, next scroll direction should be top",
19673        );
19674
19675        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19676        assert_eq!(
19677            e.next_scroll_position,
19678            NextScrollCursorCenterTopBottom::Bottom,
19679            "After top, next scroll direction should be bottom",
19680        );
19681
19682        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19683        assert_eq!(
19684            e.next_scroll_position,
19685            NextScrollCursorCenterTopBottom::Center,
19686            "After bottom, scrolling should start over",
19687        );
19688
19689        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19690        assert_eq!(
19691            e.next_scroll_position,
19692            NextScrollCursorCenterTopBottom::Top,
19693            "Scrolling continues if retriggered fast enough"
19694        );
19695    });
19696
19697    cx.executor()
19698        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19699    cx.executor().run_until_parked();
19700    cx.update_editor(|e, _, _| {
19701        assert_eq!(
19702            e.next_scroll_position,
19703            NextScrollCursorCenterTopBottom::Center,
19704            "If scrolling is not triggered fast enough, it should reset"
19705        );
19706    });
19707}
19708
19709#[gpui::test]
19710async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19711    init_test(cx, |_| {});
19712    let mut cx = EditorLspTestContext::new_rust(
19713        lsp::ServerCapabilities {
19714            definition_provider: Some(lsp::OneOf::Left(true)),
19715            references_provider: Some(lsp::OneOf::Left(true)),
19716            ..lsp::ServerCapabilities::default()
19717        },
19718        cx,
19719    )
19720    .await;
19721
19722    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19723        let go_to_definition = cx
19724            .lsp
19725            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19726                move |params, _| async move {
19727                    if empty_go_to_definition {
19728                        Ok(None)
19729                    } else {
19730                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19731                            uri: params.text_document_position_params.text_document.uri,
19732                            range: lsp::Range::new(
19733                                lsp::Position::new(4, 3),
19734                                lsp::Position::new(4, 6),
19735                            ),
19736                        })))
19737                    }
19738                },
19739            );
19740        let references = cx
19741            .lsp
19742            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19743                Ok(Some(vec![lsp::Location {
19744                    uri: params.text_document_position.text_document.uri,
19745                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19746                }]))
19747            });
19748        (go_to_definition, references)
19749    };
19750
19751    cx.set_state(
19752        &r#"fn one() {
19753            let mut a = ˇtwo();
19754        }
19755
19756        fn two() {}"#
19757            .unindent(),
19758    );
19759    set_up_lsp_handlers(false, &mut cx);
19760    let navigated = cx
19761        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19762        .await
19763        .expect("Failed to navigate to definition");
19764    assert_eq!(
19765        navigated,
19766        Navigated::Yes,
19767        "Should have navigated to definition from the GetDefinition response"
19768    );
19769    cx.assert_editor_state(
19770        &r#"fn one() {
19771            let mut a = two();
19772        }
19773
19774        fn «twoˇ»() {}"#
19775            .unindent(),
19776    );
19777
19778    let editors = cx.update_workspace(|workspace, _, cx| {
19779        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19780    });
19781    cx.update_editor(|_, _, test_editor_cx| {
19782        assert_eq!(
19783            editors.len(),
19784            1,
19785            "Initially, only one, test, editor should be open in the workspace"
19786        );
19787        assert_eq!(
19788            test_editor_cx.entity(),
19789            editors.last().expect("Asserted len is 1").clone()
19790        );
19791    });
19792
19793    set_up_lsp_handlers(true, &mut cx);
19794    let navigated = cx
19795        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19796        .await
19797        .expect("Failed to navigate to lookup references");
19798    assert_eq!(
19799        navigated,
19800        Navigated::Yes,
19801        "Should have navigated to references as a fallback after empty GoToDefinition response"
19802    );
19803    // We should not change the selections in the existing file,
19804    // if opening another milti buffer with the references
19805    cx.assert_editor_state(
19806        &r#"fn one() {
19807            let mut a = two();
19808        }
19809
19810        fn «twoˇ»() {}"#
19811            .unindent(),
19812    );
19813    let editors = cx.update_workspace(|workspace, _, cx| {
19814        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19815    });
19816    cx.update_editor(|_, _, test_editor_cx| {
19817        assert_eq!(
19818            editors.len(),
19819            2,
19820            "After falling back to references search, we open a new editor with the results"
19821        );
19822        let references_fallback_text = editors
19823            .into_iter()
19824            .find(|new_editor| *new_editor != test_editor_cx.entity())
19825            .expect("Should have one non-test editor now")
19826            .read(test_editor_cx)
19827            .text(test_editor_cx);
19828        assert_eq!(
19829            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19830            "Should use the range from the references response and not the GoToDefinition one"
19831        );
19832    });
19833}
19834
19835#[gpui::test]
19836async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19837    init_test(cx, |_| {});
19838    cx.update(|cx| {
19839        let mut editor_settings = EditorSettings::get_global(cx).clone();
19840        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19841        EditorSettings::override_global(editor_settings, cx);
19842    });
19843    let mut cx = EditorLspTestContext::new_rust(
19844        lsp::ServerCapabilities {
19845            definition_provider: Some(lsp::OneOf::Left(true)),
19846            references_provider: Some(lsp::OneOf::Left(true)),
19847            ..lsp::ServerCapabilities::default()
19848        },
19849        cx,
19850    )
19851    .await;
19852    let original_state = r#"fn one() {
19853        let mut a = ˇtwo();
19854    }
19855
19856    fn two() {}"#
19857        .unindent();
19858    cx.set_state(&original_state);
19859
19860    let mut go_to_definition = cx
19861        .lsp
19862        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19863            move |_, _| async move { Ok(None) },
19864        );
19865    let _references = cx
19866        .lsp
19867        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19868            panic!("Should not call for references with no go to definition fallback")
19869        });
19870
19871    let navigated = cx
19872        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19873        .await
19874        .expect("Failed to navigate to lookup references");
19875    go_to_definition
19876        .next()
19877        .await
19878        .expect("Should have called the go_to_definition handler");
19879
19880    assert_eq!(
19881        navigated,
19882        Navigated::No,
19883        "Should have navigated to references as a fallback after empty GoToDefinition response"
19884    );
19885    cx.assert_editor_state(&original_state);
19886    let editors = cx.update_workspace(|workspace, _, cx| {
19887        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19888    });
19889    cx.update_editor(|_, _, _| {
19890        assert_eq!(
19891            editors.len(),
19892            1,
19893            "After unsuccessful fallback, no other editor should have been opened"
19894        );
19895    });
19896}
19897
19898#[gpui::test]
19899async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19900    init_test(cx, |_| {});
19901
19902    let language = Arc::new(Language::new(
19903        LanguageConfig::default(),
19904        Some(tree_sitter_rust::LANGUAGE.into()),
19905    ));
19906
19907    let text = r#"
19908        #[cfg(test)]
19909        mod tests() {
19910            #[test]
19911            fn runnable_1() {
19912                let a = 1;
19913            }
19914
19915            #[test]
19916            fn runnable_2() {
19917                let a = 1;
19918                let b = 2;
19919            }
19920        }
19921    "#
19922    .unindent();
19923
19924    let fs = FakeFs::new(cx.executor());
19925    fs.insert_file("/file.rs", Default::default()).await;
19926
19927    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19928    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19929    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19930    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19931    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19932
19933    let editor = cx.new_window_entity(|window, cx| {
19934        Editor::new(
19935            EditorMode::full(),
19936            multi_buffer,
19937            Some(project.clone()),
19938            window,
19939            cx,
19940        )
19941    });
19942
19943    editor.update_in(cx, |editor, window, cx| {
19944        let snapshot = editor.buffer().read(cx).snapshot(cx);
19945        editor.tasks.insert(
19946            (buffer.read(cx).remote_id(), 3),
19947            RunnableTasks {
19948                templates: vec![],
19949                offset: snapshot.anchor_before(43),
19950                column: 0,
19951                extra_variables: HashMap::default(),
19952                context_range: BufferOffset(43)..BufferOffset(85),
19953            },
19954        );
19955        editor.tasks.insert(
19956            (buffer.read(cx).remote_id(), 8),
19957            RunnableTasks {
19958                templates: vec![],
19959                offset: snapshot.anchor_before(86),
19960                column: 0,
19961                extra_variables: HashMap::default(),
19962                context_range: BufferOffset(86)..BufferOffset(191),
19963            },
19964        );
19965
19966        // Test finding task when cursor is inside function body
19967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19968            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19969        });
19970        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19971        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19972
19973        // Test finding task when cursor is on function name
19974        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19975            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19976        });
19977        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19978        assert_eq!(row, 8, "Should find task when cursor is on function name");
19979    });
19980}
19981
19982#[gpui::test]
19983async fn test_folding_buffers(cx: &mut TestAppContext) {
19984    init_test(cx, |_| {});
19985
19986    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19987    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19988    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19989
19990    let fs = FakeFs::new(cx.executor());
19991    fs.insert_tree(
19992        path!("/a"),
19993        json!({
19994            "first.rs": sample_text_1,
19995            "second.rs": sample_text_2,
19996            "third.rs": sample_text_3,
19997        }),
19998    )
19999    .await;
20000    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20001    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20002    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20003    let worktree = project.update(cx, |project, cx| {
20004        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20005        assert_eq!(worktrees.len(), 1);
20006        worktrees.pop().unwrap()
20007    });
20008    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20009
20010    let buffer_1 = project
20011        .update(cx, |project, cx| {
20012            project.open_buffer((worktree_id, "first.rs"), cx)
20013        })
20014        .await
20015        .unwrap();
20016    let buffer_2 = project
20017        .update(cx, |project, cx| {
20018            project.open_buffer((worktree_id, "second.rs"), cx)
20019        })
20020        .await
20021        .unwrap();
20022    let buffer_3 = project
20023        .update(cx, |project, cx| {
20024            project.open_buffer((worktree_id, "third.rs"), cx)
20025        })
20026        .await
20027        .unwrap();
20028
20029    let multi_buffer = cx.new(|cx| {
20030        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20031        multi_buffer.push_excerpts(
20032            buffer_1.clone(),
20033            [
20034                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20035                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20036                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20037            ],
20038            cx,
20039        );
20040        multi_buffer.push_excerpts(
20041            buffer_2.clone(),
20042            [
20043                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20044                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20045                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20046            ],
20047            cx,
20048        );
20049        multi_buffer.push_excerpts(
20050            buffer_3.clone(),
20051            [
20052                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20053                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20054                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20055            ],
20056            cx,
20057        );
20058        multi_buffer
20059    });
20060    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20061        Editor::new(
20062            EditorMode::full(),
20063            multi_buffer.clone(),
20064            Some(project.clone()),
20065            window,
20066            cx,
20067        )
20068    });
20069
20070    assert_eq!(
20071        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20072        "\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",
20073    );
20074
20075    multi_buffer_editor.update(cx, |editor, cx| {
20076        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20077    });
20078    assert_eq!(
20079        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20080        "\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",
20081        "After folding the first buffer, its text should not be displayed"
20082    );
20083
20084    multi_buffer_editor.update(cx, |editor, cx| {
20085        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20086    });
20087    assert_eq!(
20088        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20089        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20090        "After folding the second buffer, its text should not be displayed"
20091    );
20092
20093    multi_buffer_editor.update(cx, |editor, cx| {
20094        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20095    });
20096    assert_eq!(
20097        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20098        "\n\n\n\n\n",
20099        "After folding the third buffer, its text should not be displayed"
20100    );
20101
20102    // Emulate selection inside the fold logic, that should work
20103    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20104        editor
20105            .snapshot(window, cx)
20106            .next_line_boundary(Point::new(0, 4));
20107    });
20108
20109    multi_buffer_editor.update(cx, |editor, cx| {
20110        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20111    });
20112    assert_eq!(
20113        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20114        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20115        "After unfolding the second buffer, its text should be displayed"
20116    );
20117
20118    // Typing inside of buffer 1 causes that buffer to be unfolded.
20119    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20120        assert_eq!(
20121            multi_buffer
20122                .read(cx)
20123                .snapshot(cx)
20124                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20125                .collect::<String>(),
20126            "bbbb"
20127        );
20128        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20129            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20130        });
20131        editor.handle_input("B", window, cx);
20132    });
20133
20134    assert_eq!(
20135        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20136        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20137        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20138    );
20139
20140    multi_buffer_editor.update(cx, |editor, cx| {
20141        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20142    });
20143    assert_eq!(
20144        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20145        "\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",
20146        "After unfolding the all buffers, all original text should be displayed"
20147    );
20148}
20149
20150#[gpui::test]
20151async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20152    init_test(cx, |_| {});
20153
20154    let sample_text_1 = "1111\n2222\n3333".to_string();
20155    let sample_text_2 = "4444\n5555\n6666".to_string();
20156    let sample_text_3 = "7777\n8888\n9999".to_string();
20157
20158    let fs = FakeFs::new(cx.executor());
20159    fs.insert_tree(
20160        path!("/a"),
20161        json!({
20162            "first.rs": sample_text_1,
20163            "second.rs": sample_text_2,
20164            "third.rs": sample_text_3,
20165        }),
20166    )
20167    .await;
20168    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20169    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20170    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20171    let worktree = project.update(cx, |project, cx| {
20172        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20173        assert_eq!(worktrees.len(), 1);
20174        worktrees.pop().unwrap()
20175    });
20176    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20177
20178    let buffer_1 = project
20179        .update(cx, |project, cx| {
20180            project.open_buffer((worktree_id, "first.rs"), cx)
20181        })
20182        .await
20183        .unwrap();
20184    let buffer_2 = project
20185        .update(cx, |project, cx| {
20186            project.open_buffer((worktree_id, "second.rs"), cx)
20187        })
20188        .await
20189        .unwrap();
20190    let buffer_3 = project
20191        .update(cx, |project, cx| {
20192            project.open_buffer((worktree_id, "third.rs"), cx)
20193        })
20194        .await
20195        .unwrap();
20196
20197    let multi_buffer = cx.new(|cx| {
20198        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20199        multi_buffer.push_excerpts(
20200            buffer_1.clone(),
20201            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20202            cx,
20203        );
20204        multi_buffer.push_excerpts(
20205            buffer_2.clone(),
20206            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20207            cx,
20208        );
20209        multi_buffer.push_excerpts(
20210            buffer_3.clone(),
20211            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20212            cx,
20213        );
20214        multi_buffer
20215    });
20216
20217    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20218        Editor::new(
20219            EditorMode::full(),
20220            multi_buffer,
20221            Some(project.clone()),
20222            window,
20223            cx,
20224        )
20225    });
20226
20227    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20228    assert_eq!(
20229        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20230        full_text,
20231    );
20232
20233    multi_buffer_editor.update(cx, |editor, cx| {
20234        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20235    });
20236    assert_eq!(
20237        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20238        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20239        "After folding the first buffer, its text should not be displayed"
20240    );
20241
20242    multi_buffer_editor.update(cx, |editor, cx| {
20243        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20244    });
20245
20246    assert_eq!(
20247        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20248        "\n\n\n\n\n\n7777\n8888\n9999",
20249        "After folding the second buffer, its text should not be displayed"
20250    );
20251
20252    multi_buffer_editor.update(cx, |editor, cx| {
20253        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20254    });
20255    assert_eq!(
20256        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20257        "\n\n\n\n\n",
20258        "After folding the third buffer, its text should not be displayed"
20259    );
20260
20261    multi_buffer_editor.update(cx, |editor, cx| {
20262        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20263    });
20264    assert_eq!(
20265        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20266        "\n\n\n\n4444\n5555\n6666\n\n",
20267        "After unfolding the second buffer, its text should be displayed"
20268    );
20269
20270    multi_buffer_editor.update(cx, |editor, cx| {
20271        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20272    });
20273    assert_eq!(
20274        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20275        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20276        "After unfolding the first buffer, its text should be displayed"
20277    );
20278
20279    multi_buffer_editor.update(cx, |editor, cx| {
20280        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20281    });
20282    assert_eq!(
20283        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20284        full_text,
20285        "After unfolding all buffers, all original text should be displayed"
20286    );
20287}
20288
20289#[gpui::test]
20290async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20291    init_test(cx, |_| {});
20292
20293    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20294
20295    let fs = FakeFs::new(cx.executor());
20296    fs.insert_tree(
20297        path!("/a"),
20298        json!({
20299            "main.rs": sample_text,
20300        }),
20301    )
20302    .await;
20303    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20304    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20305    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20306    let worktree = project.update(cx, |project, cx| {
20307        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20308        assert_eq!(worktrees.len(), 1);
20309        worktrees.pop().unwrap()
20310    });
20311    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20312
20313    let buffer_1 = project
20314        .update(cx, |project, cx| {
20315            project.open_buffer((worktree_id, "main.rs"), cx)
20316        })
20317        .await
20318        .unwrap();
20319
20320    let multi_buffer = cx.new(|cx| {
20321        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20322        multi_buffer.push_excerpts(
20323            buffer_1.clone(),
20324            [ExcerptRange::new(
20325                Point::new(0, 0)
20326                    ..Point::new(
20327                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20328                        0,
20329                    ),
20330            )],
20331            cx,
20332        );
20333        multi_buffer
20334    });
20335    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20336        Editor::new(
20337            EditorMode::full(),
20338            multi_buffer,
20339            Some(project.clone()),
20340            window,
20341            cx,
20342        )
20343    });
20344
20345    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20346    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20347        enum TestHighlight {}
20348        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20349        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20350        editor.highlight_text::<TestHighlight>(
20351            vec![highlight_range.clone()],
20352            HighlightStyle::color(Hsla::green()),
20353            cx,
20354        );
20355        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20356            s.select_ranges(Some(highlight_range))
20357        });
20358    });
20359
20360    let full_text = format!("\n\n{sample_text}");
20361    assert_eq!(
20362        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20363        full_text,
20364    );
20365}
20366
20367#[gpui::test]
20368async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20369    init_test(cx, |_| {});
20370    cx.update(|cx| {
20371        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20372            "keymaps/default-linux.json",
20373            cx,
20374        )
20375        .unwrap();
20376        cx.bind_keys(default_key_bindings);
20377    });
20378
20379    let (editor, cx) = cx.add_window_view(|window, cx| {
20380        let multi_buffer = MultiBuffer::build_multi(
20381            [
20382                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20383                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20384                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20385                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20386            ],
20387            cx,
20388        );
20389        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20390
20391        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20392        // fold all but the second buffer, so that we test navigating between two
20393        // adjacent folded buffers, as well as folded buffers at the start and
20394        // end the multibuffer
20395        editor.fold_buffer(buffer_ids[0], cx);
20396        editor.fold_buffer(buffer_ids[2], cx);
20397        editor.fold_buffer(buffer_ids[3], cx);
20398
20399        editor
20400    });
20401    cx.simulate_resize(size(px(1000.), px(1000.)));
20402
20403    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20404    cx.assert_excerpts_with_selections(indoc! {"
20405        [EXCERPT]
20406        ˇ[FOLDED]
20407        [EXCERPT]
20408        a1
20409        b1
20410        [EXCERPT]
20411        [FOLDED]
20412        [EXCERPT]
20413        [FOLDED]
20414        "
20415    });
20416    cx.simulate_keystroke("down");
20417    cx.assert_excerpts_with_selections(indoc! {"
20418        [EXCERPT]
20419        [FOLDED]
20420        [EXCERPT]
20421        ˇa1
20422        b1
20423        [EXCERPT]
20424        [FOLDED]
20425        [EXCERPT]
20426        [FOLDED]
20427        "
20428    });
20429    cx.simulate_keystroke("down");
20430    cx.assert_excerpts_with_selections(indoc! {"
20431        [EXCERPT]
20432        [FOLDED]
20433        [EXCERPT]
20434        a1
20435        ˇb1
20436        [EXCERPT]
20437        [FOLDED]
20438        [EXCERPT]
20439        [FOLDED]
20440        "
20441    });
20442    cx.simulate_keystroke("down");
20443    cx.assert_excerpts_with_selections(indoc! {"
20444        [EXCERPT]
20445        [FOLDED]
20446        [EXCERPT]
20447        a1
20448        b1
20449        ˇ[EXCERPT]
20450        [FOLDED]
20451        [EXCERPT]
20452        [FOLDED]
20453        "
20454    });
20455    cx.simulate_keystroke("down");
20456    cx.assert_excerpts_with_selections(indoc! {"
20457        [EXCERPT]
20458        [FOLDED]
20459        [EXCERPT]
20460        a1
20461        b1
20462        [EXCERPT]
20463        ˇ[FOLDED]
20464        [EXCERPT]
20465        [FOLDED]
20466        "
20467    });
20468    for _ in 0..5 {
20469        cx.simulate_keystroke("down");
20470        cx.assert_excerpts_with_selections(indoc! {"
20471            [EXCERPT]
20472            [FOLDED]
20473            [EXCERPT]
20474            a1
20475            b1
20476            [EXCERPT]
20477            [FOLDED]
20478            [EXCERPT]
20479            ˇ[FOLDED]
20480            "
20481        });
20482    }
20483
20484    cx.simulate_keystroke("up");
20485    cx.assert_excerpts_with_selections(indoc! {"
20486        [EXCERPT]
20487        [FOLDED]
20488        [EXCERPT]
20489        a1
20490        b1
20491        [EXCERPT]
20492        ˇ[FOLDED]
20493        [EXCERPT]
20494        [FOLDED]
20495        "
20496    });
20497    cx.simulate_keystroke("up");
20498    cx.assert_excerpts_with_selections(indoc! {"
20499        [EXCERPT]
20500        [FOLDED]
20501        [EXCERPT]
20502        a1
20503        b1
20504        ˇ[EXCERPT]
20505        [FOLDED]
20506        [EXCERPT]
20507        [FOLDED]
20508        "
20509    });
20510    cx.simulate_keystroke("up");
20511    cx.assert_excerpts_with_selections(indoc! {"
20512        [EXCERPT]
20513        [FOLDED]
20514        [EXCERPT]
20515        a1
20516        ˇb1
20517        [EXCERPT]
20518        [FOLDED]
20519        [EXCERPT]
20520        [FOLDED]
20521        "
20522    });
20523    cx.simulate_keystroke("up");
20524    cx.assert_excerpts_with_selections(indoc! {"
20525        [EXCERPT]
20526        [FOLDED]
20527        [EXCERPT]
20528        ˇa1
20529        b1
20530        [EXCERPT]
20531        [FOLDED]
20532        [EXCERPT]
20533        [FOLDED]
20534        "
20535    });
20536    for _ in 0..5 {
20537        cx.simulate_keystroke("up");
20538        cx.assert_excerpts_with_selections(indoc! {"
20539            [EXCERPT]
20540            ˇ[FOLDED]
20541            [EXCERPT]
20542            a1
20543            b1
20544            [EXCERPT]
20545            [FOLDED]
20546            [EXCERPT]
20547            [FOLDED]
20548            "
20549        });
20550    }
20551}
20552
20553#[gpui::test]
20554async fn test_inline_completion_text(cx: &mut TestAppContext) {
20555    init_test(cx, |_| {});
20556
20557    // Simple insertion
20558    assert_highlighted_edits(
20559        "Hello, world!",
20560        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20561        true,
20562        cx,
20563        |highlighted_edits, cx| {
20564            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20565            assert_eq!(highlighted_edits.highlights.len(), 1);
20566            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20567            assert_eq!(
20568                highlighted_edits.highlights[0].1.background_color,
20569                Some(cx.theme().status().created_background)
20570            );
20571        },
20572    )
20573    .await;
20574
20575    // Replacement
20576    assert_highlighted_edits(
20577        "This is a test.",
20578        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20579        false,
20580        cx,
20581        |highlighted_edits, cx| {
20582            assert_eq!(highlighted_edits.text, "That is a test.");
20583            assert_eq!(highlighted_edits.highlights.len(), 1);
20584            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20585            assert_eq!(
20586                highlighted_edits.highlights[0].1.background_color,
20587                Some(cx.theme().status().created_background)
20588            );
20589        },
20590    )
20591    .await;
20592
20593    // Multiple edits
20594    assert_highlighted_edits(
20595        "Hello, world!",
20596        vec![
20597            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20598            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20599        ],
20600        false,
20601        cx,
20602        |highlighted_edits, cx| {
20603            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20604            assert_eq!(highlighted_edits.highlights.len(), 2);
20605            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20606            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20607            assert_eq!(
20608                highlighted_edits.highlights[0].1.background_color,
20609                Some(cx.theme().status().created_background)
20610            );
20611            assert_eq!(
20612                highlighted_edits.highlights[1].1.background_color,
20613                Some(cx.theme().status().created_background)
20614            );
20615        },
20616    )
20617    .await;
20618
20619    // Multiple lines with edits
20620    assert_highlighted_edits(
20621        "First line\nSecond line\nThird line\nFourth line",
20622        vec![
20623            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20624            (
20625                Point::new(2, 0)..Point::new(2, 10),
20626                "New third line".to_string(),
20627            ),
20628            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20629        ],
20630        false,
20631        cx,
20632        |highlighted_edits, cx| {
20633            assert_eq!(
20634                highlighted_edits.text,
20635                "Second modified\nNew third line\nFourth updated line"
20636            );
20637            assert_eq!(highlighted_edits.highlights.len(), 3);
20638            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20639            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20640            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20641            for highlight in &highlighted_edits.highlights {
20642                assert_eq!(
20643                    highlight.1.background_color,
20644                    Some(cx.theme().status().created_background)
20645                );
20646            }
20647        },
20648    )
20649    .await;
20650}
20651
20652#[gpui::test]
20653async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20654    init_test(cx, |_| {});
20655
20656    // Deletion
20657    assert_highlighted_edits(
20658        "Hello, world!",
20659        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20660        true,
20661        cx,
20662        |highlighted_edits, cx| {
20663            assert_eq!(highlighted_edits.text, "Hello, world!");
20664            assert_eq!(highlighted_edits.highlights.len(), 1);
20665            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20666            assert_eq!(
20667                highlighted_edits.highlights[0].1.background_color,
20668                Some(cx.theme().status().deleted_background)
20669            );
20670        },
20671    )
20672    .await;
20673
20674    // Insertion
20675    assert_highlighted_edits(
20676        "Hello, world!",
20677        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20678        true,
20679        cx,
20680        |highlighted_edits, cx| {
20681            assert_eq!(highlighted_edits.highlights.len(), 1);
20682            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20683            assert_eq!(
20684                highlighted_edits.highlights[0].1.background_color,
20685                Some(cx.theme().status().created_background)
20686            );
20687        },
20688    )
20689    .await;
20690}
20691
20692async fn assert_highlighted_edits(
20693    text: &str,
20694    edits: Vec<(Range<Point>, String)>,
20695    include_deletions: bool,
20696    cx: &mut TestAppContext,
20697    assertion_fn: impl Fn(HighlightedText, &App),
20698) {
20699    let window = cx.add_window(|window, cx| {
20700        let buffer = MultiBuffer::build_simple(text, cx);
20701        Editor::new(EditorMode::full(), buffer, None, window, cx)
20702    });
20703    let cx = &mut VisualTestContext::from_window(*window, cx);
20704
20705    let (buffer, snapshot) = window
20706        .update(cx, |editor, _window, cx| {
20707            (
20708                editor.buffer().clone(),
20709                editor.buffer().read(cx).snapshot(cx),
20710            )
20711        })
20712        .unwrap();
20713
20714    let edits = edits
20715        .into_iter()
20716        .map(|(range, edit)| {
20717            (
20718                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20719                edit,
20720            )
20721        })
20722        .collect::<Vec<_>>();
20723
20724    let text_anchor_edits = edits
20725        .clone()
20726        .into_iter()
20727        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20728        .collect::<Vec<_>>();
20729
20730    let edit_preview = window
20731        .update(cx, |_, _window, cx| {
20732            buffer
20733                .read(cx)
20734                .as_singleton()
20735                .unwrap()
20736                .read(cx)
20737                .preview_edits(text_anchor_edits.into(), cx)
20738        })
20739        .unwrap()
20740        .await;
20741
20742    cx.update(|_window, cx| {
20743        let highlighted_edits = inline_completion_edit_text(
20744            &snapshot.as_singleton().unwrap().2,
20745            &edits,
20746            &edit_preview,
20747            include_deletions,
20748            cx,
20749        );
20750        assertion_fn(highlighted_edits, cx)
20751    });
20752}
20753
20754#[track_caller]
20755fn assert_breakpoint(
20756    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20757    path: &Arc<Path>,
20758    expected: Vec<(u32, Breakpoint)>,
20759) {
20760    if expected.len() == 0usize {
20761        assert!(!breakpoints.contains_key(path), "{}", path.display());
20762    } else {
20763        let mut breakpoint = breakpoints
20764            .get(path)
20765            .unwrap()
20766            .into_iter()
20767            .map(|breakpoint| {
20768                (
20769                    breakpoint.row,
20770                    Breakpoint {
20771                        message: breakpoint.message.clone(),
20772                        state: breakpoint.state,
20773                        condition: breakpoint.condition.clone(),
20774                        hit_condition: breakpoint.hit_condition.clone(),
20775                    },
20776                )
20777            })
20778            .collect::<Vec<_>>();
20779
20780        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20781
20782        assert_eq!(expected, breakpoint);
20783    }
20784}
20785
20786fn add_log_breakpoint_at_cursor(
20787    editor: &mut Editor,
20788    log_message: &str,
20789    window: &mut Window,
20790    cx: &mut Context<Editor>,
20791) {
20792    let (anchor, bp) = editor
20793        .breakpoints_at_cursors(window, cx)
20794        .first()
20795        .and_then(|(anchor, bp)| {
20796            if let Some(bp) = bp {
20797                Some((*anchor, bp.clone()))
20798            } else {
20799                None
20800            }
20801        })
20802        .unwrap_or_else(|| {
20803            let cursor_position: Point = editor.selections.newest(cx).head();
20804
20805            let breakpoint_position = editor
20806                .snapshot(window, cx)
20807                .display_snapshot
20808                .buffer_snapshot
20809                .anchor_before(Point::new(cursor_position.row, 0));
20810
20811            (breakpoint_position, Breakpoint::new_log(&log_message))
20812        });
20813
20814    editor.edit_breakpoint_at_anchor(
20815        anchor,
20816        bp,
20817        BreakpointEditAction::EditLogMessage(log_message.into()),
20818        cx,
20819    );
20820}
20821
20822#[gpui::test]
20823async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20824    init_test(cx, |_| {});
20825
20826    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20827    let fs = FakeFs::new(cx.executor());
20828    fs.insert_tree(
20829        path!("/a"),
20830        json!({
20831            "main.rs": sample_text,
20832        }),
20833    )
20834    .await;
20835    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20836    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20837    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20838
20839    let fs = FakeFs::new(cx.executor());
20840    fs.insert_tree(
20841        path!("/a"),
20842        json!({
20843            "main.rs": sample_text,
20844        }),
20845    )
20846    .await;
20847    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20848    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20849    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20850    let worktree_id = workspace
20851        .update(cx, |workspace, _window, cx| {
20852            workspace.project().update(cx, |project, cx| {
20853                project.worktrees(cx).next().unwrap().read(cx).id()
20854            })
20855        })
20856        .unwrap();
20857
20858    let buffer = project
20859        .update(cx, |project, cx| {
20860            project.open_buffer((worktree_id, "main.rs"), cx)
20861        })
20862        .await
20863        .unwrap();
20864
20865    let (editor, cx) = cx.add_window_view(|window, cx| {
20866        Editor::new(
20867            EditorMode::full(),
20868            MultiBuffer::build_from_buffer(buffer, cx),
20869            Some(project.clone()),
20870            window,
20871            cx,
20872        )
20873    });
20874
20875    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20876    let abs_path = project.read_with(cx, |project, cx| {
20877        project
20878            .absolute_path(&project_path, cx)
20879            .map(|path_buf| Arc::from(path_buf.to_owned()))
20880            .unwrap()
20881    });
20882
20883    // assert we can add breakpoint on the first line
20884    editor.update_in(cx, |editor, window, cx| {
20885        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20886        editor.move_to_end(&MoveToEnd, window, cx);
20887        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20888    });
20889
20890    let breakpoints = editor.update(cx, |editor, cx| {
20891        editor
20892            .breakpoint_store()
20893            .as_ref()
20894            .unwrap()
20895            .read(cx)
20896            .all_source_breakpoints(cx)
20897            .clone()
20898    });
20899
20900    assert_eq!(1, breakpoints.len());
20901    assert_breakpoint(
20902        &breakpoints,
20903        &abs_path,
20904        vec![
20905            (0, Breakpoint::new_standard()),
20906            (3, Breakpoint::new_standard()),
20907        ],
20908    );
20909
20910    editor.update_in(cx, |editor, window, cx| {
20911        editor.move_to_beginning(&MoveToBeginning, window, cx);
20912        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20913    });
20914
20915    let breakpoints = editor.update(cx, |editor, cx| {
20916        editor
20917            .breakpoint_store()
20918            .as_ref()
20919            .unwrap()
20920            .read(cx)
20921            .all_source_breakpoints(cx)
20922            .clone()
20923    });
20924
20925    assert_eq!(1, breakpoints.len());
20926    assert_breakpoint(
20927        &breakpoints,
20928        &abs_path,
20929        vec![(3, Breakpoint::new_standard())],
20930    );
20931
20932    editor.update_in(cx, |editor, window, cx| {
20933        editor.move_to_end(&MoveToEnd, window, cx);
20934        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20935    });
20936
20937    let breakpoints = editor.update(cx, |editor, cx| {
20938        editor
20939            .breakpoint_store()
20940            .as_ref()
20941            .unwrap()
20942            .read(cx)
20943            .all_source_breakpoints(cx)
20944            .clone()
20945    });
20946
20947    assert_eq!(0, breakpoints.len());
20948    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20949}
20950
20951#[gpui::test]
20952async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20953    init_test(cx, |_| {});
20954
20955    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20956
20957    let fs = FakeFs::new(cx.executor());
20958    fs.insert_tree(
20959        path!("/a"),
20960        json!({
20961            "main.rs": sample_text,
20962        }),
20963    )
20964    .await;
20965    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20966    let (workspace, cx) =
20967        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20968
20969    let worktree_id = workspace.update(cx, |workspace, cx| {
20970        workspace.project().update(cx, |project, cx| {
20971            project.worktrees(cx).next().unwrap().read(cx).id()
20972        })
20973    });
20974
20975    let buffer = project
20976        .update(cx, |project, cx| {
20977            project.open_buffer((worktree_id, "main.rs"), cx)
20978        })
20979        .await
20980        .unwrap();
20981
20982    let (editor, cx) = cx.add_window_view(|window, cx| {
20983        Editor::new(
20984            EditorMode::full(),
20985            MultiBuffer::build_from_buffer(buffer, cx),
20986            Some(project.clone()),
20987            window,
20988            cx,
20989        )
20990    });
20991
20992    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20993    let abs_path = project.read_with(cx, |project, cx| {
20994        project
20995            .absolute_path(&project_path, cx)
20996            .map(|path_buf| Arc::from(path_buf.to_owned()))
20997            .unwrap()
20998    });
20999
21000    editor.update_in(cx, |editor, window, cx| {
21001        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21002    });
21003
21004    let breakpoints = editor.update(cx, |editor, cx| {
21005        editor
21006            .breakpoint_store()
21007            .as_ref()
21008            .unwrap()
21009            .read(cx)
21010            .all_source_breakpoints(cx)
21011            .clone()
21012    });
21013
21014    assert_breakpoint(
21015        &breakpoints,
21016        &abs_path,
21017        vec![(0, Breakpoint::new_log("hello world"))],
21018    );
21019
21020    // Removing a log message from a log breakpoint should remove it
21021    editor.update_in(cx, |editor, window, cx| {
21022        add_log_breakpoint_at_cursor(editor, "", window, cx);
21023    });
21024
21025    let breakpoints = editor.update(cx, |editor, cx| {
21026        editor
21027            .breakpoint_store()
21028            .as_ref()
21029            .unwrap()
21030            .read(cx)
21031            .all_source_breakpoints(cx)
21032            .clone()
21033    });
21034
21035    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21036
21037    editor.update_in(cx, |editor, window, cx| {
21038        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21039        editor.move_to_end(&MoveToEnd, window, cx);
21040        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21041        // Not adding a log message to a standard breakpoint shouldn't remove it
21042        add_log_breakpoint_at_cursor(editor, "", window, cx);
21043    });
21044
21045    let breakpoints = editor.update(cx, |editor, cx| {
21046        editor
21047            .breakpoint_store()
21048            .as_ref()
21049            .unwrap()
21050            .read(cx)
21051            .all_source_breakpoints(cx)
21052            .clone()
21053    });
21054
21055    assert_breakpoint(
21056        &breakpoints,
21057        &abs_path,
21058        vec![
21059            (0, Breakpoint::new_standard()),
21060            (3, Breakpoint::new_standard()),
21061        ],
21062    );
21063
21064    editor.update_in(cx, |editor, window, cx| {
21065        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21066    });
21067
21068    let breakpoints = editor.update(cx, |editor, cx| {
21069        editor
21070            .breakpoint_store()
21071            .as_ref()
21072            .unwrap()
21073            .read(cx)
21074            .all_source_breakpoints(cx)
21075            .clone()
21076    });
21077
21078    assert_breakpoint(
21079        &breakpoints,
21080        &abs_path,
21081        vec![
21082            (0, Breakpoint::new_standard()),
21083            (3, Breakpoint::new_log("hello world")),
21084        ],
21085    );
21086
21087    editor.update_in(cx, |editor, window, cx| {
21088        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21089    });
21090
21091    let breakpoints = editor.update(cx, |editor, cx| {
21092        editor
21093            .breakpoint_store()
21094            .as_ref()
21095            .unwrap()
21096            .read(cx)
21097            .all_source_breakpoints(cx)
21098            .clone()
21099    });
21100
21101    assert_breakpoint(
21102        &breakpoints,
21103        &abs_path,
21104        vec![
21105            (0, Breakpoint::new_standard()),
21106            (3, Breakpoint::new_log("hello Earth!!")),
21107        ],
21108    );
21109}
21110
21111/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21112/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21113/// or when breakpoints were placed out of order. This tests for a regression too
21114#[gpui::test]
21115async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21116    init_test(cx, |_| {});
21117
21118    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21119    let fs = FakeFs::new(cx.executor());
21120    fs.insert_tree(
21121        path!("/a"),
21122        json!({
21123            "main.rs": sample_text,
21124        }),
21125    )
21126    .await;
21127    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21128    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21129    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21130
21131    let fs = FakeFs::new(cx.executor());
21132    fs.insert_tree(
21133        path!("/a"),
21134        json!({
21135            "main.rs": sample_text,
21136        }),
21137    )
21138    .await;
21139    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21140    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21141    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21142    let worktree_id = workspace
21143        .update(cx, |workspace, _window, cx| {
21144            workspace.project().update(cx, |project, cx| {
21145                project.worktrees(cx).next().unwrap().read(cx).id()
21146            })
21147        })
21148        .unwrap();
21149
21150    let buffer = project
21151        .update(cx, |project, cx| {
21152            project.open_buffer((worktree_id, "main.rs"), cx)
21153        })
21154        .await
21155        .unwrap();
21156
21157    let (editor, cx) = cx.add_window_view(|window, cx| {
21158        Editor::new(
21159            EditorMode::full(),
21160            MultiBuffer::build_from_buffer(buffer, cx),
21161            Some(project.clone()),
21162            window,
21163            cx,
21164        )
21165    });
21166
21167    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21168    let abs_path = project.read_with(cx, |project, cx| {
21169        project
21170            .absolute_path(&project_path, cx)
21171            .map(|path_buf| Arc::from(path_buf.to_owned()))
21172            .unwrap()
21173    });
21174
21175    // assert we can add breakpoint on the first line
21176    editor.update_in(cx, |editor, window, cx| {
21177        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21178        editor.move_to_end(&MoveToEnd, window, cx);
21179        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21180        editor.move_up(&MoveUp, window, cx);
21181        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21182    });
21183
21184    let breakpoints = editor.update(cx, |editor, cx| {
21185        editor
21186            .breakpoint_store()
21187            .as_ref()
21188            .unwrap()
21189            .read(cx)
21190            .all_source_breakpoints(cx)
21191            .clone()
21192    });
21193
21194    assert_eq!(1, breakpoints.len());
21195    assert_breakpoint(
21196        &breakpoints,
21197        &abs_path,
21198        vec![
21199            (0, Breakpoint::new_standard()),
21200            (2, Breakpoint::new_standard()),
21201            (3, Breakpoint::new_standard()),
21202        ],
21203    );
21204
21205    editor.update_in(cx, |editor, window, cx| {
21206        editor.move_to_beginning(&MoveToBeginning, window, cx);
21207        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21208        editor.move_to_end(&MoveToEnd, window, cx);
21209        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21210        // Disabling a breakpoint that doesn't exist should do nothing
21211        editor.move_up(&MoveUp, window, cx);
21212        editor.move_up(&MoveUp, window, cx);
21213        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21214    });
21215
21216    let breakpoints = editor.update(cx, |editor, cx| {
21217        editor
21218            .breakpoint_store()
21219            .as_ref()
21220            .unwrap()
21221            .read(cx)
21222            .all_source_breakpoints(cx)
21223            .clone()
21224    });
21225
21226    let disable_breakpoint = {
21227        let mut bp = Breakpoint::new_standard();
21228        bp.state = BreakpointState::Disabled;
21229        bp
21230    };
21231
21232    assert_eq!(1, breakpoints.len());
21233    assert_breakpoint(
21234        &breakpoints,
21235        &abs_path,
21236        vec![
21237            (0, disable_breakpoint.clone()),
21238            (2, Breakpoint::new_standard()),
21239            (3, disable_breakpoint.clone()),
21240        ],
21241    );
21242
21243    editor.update_in(cx, |editor, window, cx| {
21244        editor.move_to_beginning(&MoveToBeginning, window, cx);
21245        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21246        editor.move_to_end(&MoveToEnd, window, cx);
21247        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21248        editor.move_up(&MoveUp, window, cx);
21249        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21250    });
21251
21252    let breakpoints = editor.update(cx, |editor, cx| {
21253        editor
21254            .breakpoint_store()
21255            .as_ref()
21256            .unwrap()
21257            .read(cx)
21258            .all_source_breakpoints(cx)
21259            .clone()
21260    });
21261
21262    assert_eq!(1, breakpoints.len());
21263    assert_breakpoint(
21264        &breakpoints,
21265        &abs_path,
21266        vec![
21267            (0, Breakpoint::new_standard()),
21268            (2, disable_breakpoint),
21269            (3, Breakpoint::new_standard()),
21270        ],
21271    );
21272}
21273
21274#[gpui::test]
21275async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21276    init_test(cx, |_| {});
21277    let capabilities = lsp::ServerCapabilities {
21278        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21279            prepare_provider: Some(true),
21280            work_done_progress_options: Default::default(),
21281        })),
21282        ..Default::default()
21283    };
21284    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21285
21286    cx.set_state(indoc! {"
21287        struct Fˇoo {}
21288    "});
21289
21290    cx.update_editor(|editor, _, cx| {
21291        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21292        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21293        editor.highlight_background::<DocumentHighlightRead>(
21294            &[highlight_range],
21295            |theme| theme.colors().editor_document_highlight_read_background,
21296            cx,
21297        );
21298    });
21299
21300    let mut prepare_rename_handler = cx
21301        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21302            move |_, _, _| async move {
21303                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21304                    start: lsp::Position {
21305                        line: 0,
21306                        character: 7,
21307                    },
21308                    end: lsp::Position {
21309                        line: 0,
21310                        character: 10,
21311                    },
21312                })))
21313            },
21314        );
21315    let prepare_rename_task = cx
21316        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21317        .expect("Prepare rename was not started");
21318    prepare_rename_handler.next().await.unwrap();
21319    prepare_rename_task.await.expect("Prepare rename failed");
21320
21321    let mut rename_handler =
21322        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21323            let edit = lsp::TextEdit {
21324                range: lsp::Range {
21325                    start: lsp::Position {
21326                        line: 0,
21327                        character: 7,
21328                    },
21329                    end: lsp::Position {
21330                        line: 0,
21331                        character: 10,
21332                    },
21333                },
21334                new_text: "FooRenamed".to_string(),
21335            };
21336            Ok(Some(lsp::WorkspaceEdit::new(
21337                // Specify the same edit twice
21338                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21339            )))
21340        });
21341    let rename_task = cx
21342        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21343        .expect("Confirm rename was not started");
21344    rename_handler.next().await.unwrap();
21345    rename_task.await.expect("Confirm rename failed");
21346    cx.run_until_parked();
21347
21348    // Despite two edits, only one is actually applied as those are identical
21349    cx.assert_editor_state(indoc! {"
21350        struct FooRenamedˇ {}
21351    "});
21352}
21353
21354#[gpui::test]
21355async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21356    init_test(cx, |_| {});
21357    // These capabilities indicate that the server does not support prepare rename.
21358    let capabilities = lsp::ServerCapabilities {
21359        rename_provider: Some(lsp::OneOf::Left(true)),
21360        ..Default::default()
21361    };
21362    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21363
21364    cx.set_state(indoc! {"
21365        struct Fˇoo {}
21366    "});
21367
21368    cx.update_editor(|editor, _window, cx| {
21369        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21370        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21371        editor.highlight_background::<DocumentHighlightRead>(
21372            &[highlight_range],
21373            |theme| theme.colors().editor_document_highlight_read_background,
21374            cx,
21375        );
21376    });
21377
21378    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21379        .expect("Prepare rename was not started")
21380        .await
21381        .expect("Prepare rename failed");
21382
21383    let mut rename_handler =
21384        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21385            let edit = lsp::TextEdit {
21386                range: lsp::Range {
21387                    start: lsp::Position {
21388                        line: 0,
21389                        character: 7,
21390                    },
21391                    end: lsp::Position {
21392                        line: 0,
21393                        character: 10,
21394                    },
21395                },
21396                new_text: "FooRenamed".to_string(),
21397            };
21398            Ok(Some(lsp::WorkspaceEdit::new(
21399                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21400            )))
21401        });
21402    let rename_task = cx
21403        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21404        .expect("Confirm rename was not started");
21405    rename_handler.next().await.unwrap();
21406    rename_task.await.expect("Confirm rename failed");
21407    cx.run_until_parked();
21408
21409    // Correct range is renamed, as `surrounding_word` is used to find it.
21410    cx.assert_editor_state(indoc! {"
21411        struct FooRenamedˇ {}
21412    "});
21413}
21414
21415#[gpui::test]
21416async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21417    init_test(cx, |_| {});
21418    let mut cx = EditorTestContext::new(cx).await;
21419
21420    let language = Arc::new(
21421        Language::new(
21422            LanguageConfig::default(),
21423            Some(tree_sitter_html::LANGUAGE.into()),
21424        )
21425        .with_brackets_query(
21426            r#"
21427            ("<" @open "/>" @close)
21428            ("</" @open ">" @close)
21429            ("<" @open ">" @close)
21430            ("\"" @open "\"" @close)
21431            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21432        "#,
21433        )
21434        .unwrap(),
21435    );
21436    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21437
21438    cx.set_state(indoc! {"
21439        <span>ˇ</span>
21440    "});
21441    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21442    cx.assert_editor_state(indoc! {"
21443        <span>
21444        ˇ
21445        </span>
21446    "});
21447
21448    cx.set_state(indoc! {"
21449        <span><span></span>ˇ</span>
21450    "});
21451    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21452    cx.assert_editor_state(indoc! {"
21453        <span><span></span>
21454        ˇ</span>
21455    "});
21456
21457    cx.set_state(indoc! {"
21458        <span>ˇ
21459        </span>
21460    "});
21461    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21462    cx.assert_editor_state(indoc! {"
21463        <span>
21464        ˇ
21465        </span>
21466    "});
21467}
21468
21469#[gpui::test(iterations = 10)]
21470async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21471    init_test(cx, |_| {});
21472
21473    let fs = FakeFs::new(cx.executor());
21474    fs.insert_tree(
21475        path!("/dir"),
21476        json!({
21477            "a.ts": "a",
21478        }),
21479    )
21480    .await;
21481
21482    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21483    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21484    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21485
21486    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21487    language_registry.add(Arc::new(Language::new(
21488        LanguageConfig {
21489            name: "TypeScript".into(),
21490            matcher: LanguageMatcher {
21491                path_suffixes: vec!["ts".to_string()],
21492                ..Default::default()
21493            },
21494            ..Default::default()
21495        },
21496        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21497    )));
21498    let mut fake_language_servers = language_registry.register_fake_lsp(
21499        "TypeScript",
21500        FakeLspAdapter {
21501            capabilities: lsp::ServerCapabilities {
21502                code_lens_provider: Some(lsp::CodeLensOptions {
21503                    resolve_provider: Some(true),
21504                }),
21505                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21506                    commands: vec!["_the/command".to_string()],
21507                    ..lsp::ExecuteCommandOptions::default()
21508                }),
21509                ..lsp::ServerCapabilities::default()
21510            },
21511            ..FakeLspAdapter::default()
21512        },
21513    );
21514
21515    let editor = workspace
21516        .update(cx, |workspace, window, cx| {
21517            workspace.open_abs_path(
21518                PathBuf::from(path!("/dir/a.ts")),
21519                OpenOptions::default(),
21520                window,
21521                cx,
21522            )
21523        })
21524        .unwrap()
21525        .await
21526        .unwrap()
21527        .downcast::<Editor>()
21528        .unwrap();
21529    cx.executor().run_until_parked();
21530
21531    let fake_server = fake_language_servers.next().await.unwrap();
21532
21533    let buffer = editor.update(cx, |editor, cx| {
21534        editor
21535            .buffer()
21536            .read(cx)
21537            .as_singleton()
21538            .expect("have opened a single file by path")
21539    });
21540
21541    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21542    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21543    drop(buffer_snapshot);
21544    let actions = cx
21545        .update_window(*workspace, |_, window, cx| {
21546            project.code_actions(&buffer, anchor..anchor, window, cx)
21547        })
21548        .unwrap();
21549
21550    fake_server
21551        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21552            Ok(Some(vec![
21553                lsp::CodeLens {
21554                    range: lsp::Range::default(),
21555                    command: Some(lsp::Command {
21556                        title: "Code lens command".to_owned(),
21557                        command: "_the/command".to_owned(),
21558                        arguments: None,
21559                    }),
21560                    data: None,
21561                },
21562                lsp::CodeLens {
21563                    range: lsp::Range::default(),
21564                    command: Some(lsp::Command {
21565                        title: "Command not in capabilities".to_owned(),
21566                        command: "not in capabilities".to_owned(),
21567                        arguments: None,
21568                    }),
21569                    data: None,
21570                },
21571                lsp::CodeLens {
21572                    range: lsp::Range {
21573                        start: lsp::Position {
21574                            line: 1,
21575                            character: 1,
21576                        },
21577                        end: lsp::Position {
21578                            line: 1,
21579                            character: 1,
21580                        },
21581                    },
21582                    command: Some(lsp::Command {
21583                        title: "Command not in range".to_owned(),
21584                        command: "_the/command".to_owned(),
21585                        arguments: None,
21586                    }),
21587                    data: None,
21588                },
21589            ]))
21590        })
21591        .next()
21592        .await;
21593
21594    let actions = actions.await.unwrap();
21595    assert_eq!(
21596        actions.len(),
21597        1,
21598        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21599    );
21600    let action = actions[0].clone();
21601    let apply = project.update(cx, |project, cx| {
21602        project.apply_code_action(buffer.clone(), action, true, cx)
21603    });
21604
21605    // Resolving the code action does not populate its edits. In absence of
21606    // edits, we must execute the given command.
21607    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21608        |mut lens, _| async move {
21609            let lens_command = lens.command.as_mut().expect("should have a command");
21610            assert_eq!(lens_command.title, "Code lens command");
21611            lens_command.arguments = Some(vec![json!("the-argument")]);
21612            Ok(lens)
21613        },
21614    );
21615
21616    // While executing the command, the language server sends the editor
21617    // a `workspaceEdit` request.
21618    fake_server
21619        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21620            let fake = fake_server.clone();
21621            move |params, _| {
21622                assert_eq!(params.command, "_the/command");
21623                let fake = fake.clone();
21624                async move {
21625                    fake.server
21626                        .request::<lsp::request::ApplyWorkspaceEdit>(
21627                            lsp::ApplyWorkspaceEditParams {
21628                                label: None,
21629                                edit: lsp::WorkspaceEdit {
21630                                    changes: Some(
21631                                        [(
21632                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21633                                            vec![lsp::TextEdit {
21634                                                range: lsp::Range::new(
21635                                                    lsp::Position::new(0, 0),
21636                                                    lsp::Position::new(0, 0),
21637                                                ),
21638                                                new_text: "X".into(),
21639                                            }],
21640                                        )]
21641                                        .into_iter()
21642                                        .collect(),
21643                                    ),
21644                                    ..lsp::WorkspaceEdit::default()
21645                                },
21646                            },
21647                        )
21648                        .await
21649                        .into_response()
21650                        .unwrap();
21651                    Ok(Some(json!(null)))
21652                }
21653            }
21654        })
21655        .next()
21656        .await;
21657
21658    // Applying the code lens command returns a project transaction containing the edits
21659    // sent by the language server in its `workspaceEdit` request.
21660    let transaction = apply.await.unwrap();
21661    assert!(transaction.0.contains_key(&buffer));
21662    buffer.update(cx, |buffer, cx| {
21663        assert_eq!(buffer.text(), "Xa");
21664        buffer.undo(cx);
21665        assert_eq!(buffer.text(), "a");
21666    });
21667
21668    let actions_after_edits = cx
21669        .update_window(*workspace, |_, window, cx| {
21670            project.code_actions(&buffer, anchor..anchor, window, cx)
21671        })
21672        .unwrap()
21673        .await
21674        .unwrap();
21675    assert_eq!(
21676        actions, actions_after_edits,
21677        "For the same selection, same code lens actions should be returned"
21678    );
21679
21680    let _responses =
21681        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21682            panic!("No more code lens requests are expected");
21683        });
21684    editor.update_in(cx, |editor, window, cx| {
21685        editor.select_all(&SelectAll, window, cx);
21686    });
21687    cx.executor().run_until_parked();
21688    let new_actions = cx
21689        .update_window(*workspace, |_, window, cx| {
21690            project.code_actions(&buffer, anchor..anchor, window, cx)
21691        })
21692        .unwrap()
21693        .await
21694        .unwrap();
21695    assert_eq!(
21696        actions, new_actions,
21697        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21698    );
21699}
21700
21701#[gpui::test]
21702async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21703    init_test(cx, |_| {});
21704
21705    let fs = FakeFs::new(cx.executor());
21706    let main_text = r#"fn main() {
21707println!("1");
21708println!("2");
21709println!("3");
21710println!("4");
21711println!("5");
21712}"#;
21713    let lib_text = "mod foo {}";
21714    fs.insert_tree(
21715        path!("/a"),
21716        json!({
21717            "lib.rs": lib_text,
21718            "main.rs": main_text,
21719        }),
21720    )
21721    .await;
21722
21723    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21724    let (workspace, cx) =
21725        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21726    let worktree_id = workspace.update(cx, |workspace, cx| {
21727        workspace.project().update(cx, |project, cx| {
21728            project.worktrees(cx).next().unwrap().read(cx).id()
21729        })
21730    });
21731
21732    let expected_ranges = vec![
21733        Point::new(0, 0)..Point::new(0, 0),
21734        Point::new(1, 0)..Point::new(1, 1),
21735        Point::new(2, 0)..Point::new(2, 2),
21736        Point::new(3, 0)..Point::new(3, 3),
21737    ];
21738
21739    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21740    let editor_1 = workspace
21741        .update_in(cx, |workspace, window, cx| {
21742            workspace.open_path(
21743                (worktree_id, "main.rs"),
21744                Some(pane_1.downgrade()),
21745                true,
21746                window,
21747                cx,
21748            )
21749        })
21750        .unwrap()
21751        .await
21752        .downcast::<Editor>()
21753        .unwrap();
21754    pane_1.update(cx, |pane, cx| {
21755        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21756        open_editor.update(cx, |editor, cx| {
21757            assert_eq!(
21758                editor.display_text(cx),
21759                main_text,
21760                "Original main.rs text on initial open",
21761            );
21762            assert_eq!(
21763                editor
21764                    .selections
21765                    .all::<Point>(cx)
21766                    .into_iter()
21767                    .map(|s| s.range())
21768                    .collect::<Vec<_>>(),
21769                vec![Point::zero()..Point::zero()],
21770                "Default selections on initial open",
21771            );
21772        })
21773    });
21774    editor_1.update_in(cx, |editor, window, cx| {
21775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21776            s.select_ranges(expected_ranges.clone());
21777        });
21778    });
21779
21780    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21781        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21782    });
21783    let editor_2 = workspace
21784        .update_in(cx, |workspace, window, cx| {
21785            workspace.open_path(
21786                (worktree_id, "main.rs"),
21787                Some(pane_2.downgrade()),
21788                true,
21789                window,
21790                cx,
21791            )
21792        })
21793        .unwrap()
21794        .await
21795        .downcast::<Editor>()
21796        .unwrap();
21797    pane_2.update(cx, |pane, cx| {
21798        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21799        open_editor.update(cx, |editor, cx| {
21800            assert_eq!(
21801                editor.display_text(cx),
21802                main_text,
21803                "Original main.rs text on initial open in another panel",
21804            );
21805            assert_eq!(
21806                editor
21807                    .selections
21808                    .all::<Point>(cx)
21809                    .into_iter()
21810                    .map(|s| s.range())
21811                    .collect::<Vec<_>>(),
21812                vec![Point::zero()..Point::zero()],
21813                "Default selections on initial open in another panel",
21814            );
21815        })
21816    });
21817
21818    editor_2.update_in(cx, |editor, window, cx| {
21819        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21820    });
21821
21822    let _other_editor_1 = workspace
21823        .update_in(cx, |workspace, window, cx| {
21824            workspace.open_path(
21825                (worktree_id, "lib.rs"),
21826                Some(pane_1.downgrade()),
21827                true,
21828                window,
21829                cx,
21830            )
21831        })
21832        .unwrap()
21833        .await
21834        .downcast::<Editor>()
21835        .unwrap();
21836    pane_1
21837        .update_in(cx, |pane, window, cx| {
21838            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21839        })
21840        .await
21841        .unwrap();
21842    drop(editor_1);
21843    pane_1.update(cx, |pane, cx| {
21844        pane.active_item()
21845            .unwrap()
21846            .downcast::<Editor>()
21847            .unwrap()
21848            .update(cx, |editor, cx| {
21849                assert_eq!(
21850                    editor.display_text(cx),
21851                    lib_text,
21852                    "Other file should be open and active",
21853                );
21854            });
21855        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21856    });
21857
21858    let _other_editor_2 = workspace
21859        .update_in(cx, |workspace, window, cx| {
21860            workspace.open_path(
21861                (worktree_id, "lib.rs"),
21862                Some(pane_2.downgrade()),
21863                true,
21864                window,
21865                cx,
21866            )
21867        })
21868        .unwrap()
21869        .await
21870        .downcast::<Editor>()
21871        .unwrap();
21872    pane_2
21873        .update_in(cx, |pane, window, cx| {
21874            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21875        })
21876        .await
21877        .unwrap();
21878    drop(editor_2);
21879    pane_2.update(cx, |pane, cx| {
21880        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21881        open_editor.update(cx, |editor, cx| {
21882            assert_eq!(
21883                editor.display_text(cx),
21884                lib_text,
21885                "Other file should be open and active in another panel too",
21886            );
21887        });
21888        assert_eq!(
21889            pane.items().count(),
21890            1,
21891            "No other editors should be open in another pane",
21892        );
21893    });
21894
21895    let _editor_1_reopened = workspace
21896        .update_in(cx, |workspace, window, cx| {
21897            workspace.open_path(
21898                (worktree_id, "main.rs"),
21899                Some(pane_1.downgrade()),
21900                true,
21901                window,
21902                cx,
21903            )
21904        })
21905        .unwrap()
21906        .await
21907        .downcast::<Editor>()
21908        .unwrap();
21909    let _editor_2_reopened = workspace
21910        .update_in(cx, |workspace, window, cx| {
21911            workspace.open_path(
21912                (worktree_id, "main.rs"),
21913                Some(pane_2.downgrade()),
21914                true,
21915                window,
21916                cx,
21917            )
21918        })
21919        .unwrap()
21920        .await
21921        .downcast::<Editor>()
21922        .unwrap();
21923    pane_1.update(cx, |pane, cx| {
21924        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21925        open_editor.update(cx, |editor, cx| {
21926            assert_eq!(
21927                editor.display_text(cx),
21928                main_text,
21929                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21930            );
21931            assert_eq!(
21932                editor
21933                    .selections
21934                    .all::<Point>(cx)
21935                    .into_iter()
21936                    .map(|s| s.range())
21937                    .collect::<Vec<_>>(),
21938                expected_ranges,
21939                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21940            );
21941        })
21942    });
21943    pane_2.update(cx, |pane, cx| {
21944        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21945        open_editor.update(cx, |editor, cx| {
21946            assert_eq!(
21947                editor.display_text(cx),
21948                r#"fn main() {
21949⋯rintln!("1");
21950⋯intln!("2");
21951⋯ntln!("3");
21952println!("4");
21953println!("5");
21954}"#,
21955                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21956            );
21957            assert_eq!(
21958                editor
21959                    .selections
21960                    .all::<Point>(cx)
21961                    .into_iter()
21962                    .map(|s| s.range())
21963                    .collect::<Vec<_>>(),
21964                vec![Point::zero()..Point::zero()],
21965                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21966            );
21967        })
21968    });
21969}
21970
21971#[gpui::test]
21972async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21973    init_test(cx, |_| {});
21974
21975    let fs = FakeFs::new(cx.executor());
21976    let main_text = r#"fn main() {
21977println!("1");
21978println!("2");
21979println!("3");
21980println!("4");
21981println!("5");
21982}"#;
21983    let lib_text = "mod foo {}";
21984    fs.insert_tree(
21985        path!("/a"),
21986        json!({
21987            "lib.rs": lib_text,
21988            "main.rs": main_text,
21989        }),
21990    )
21991    .await;
21992
21993    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21994    let (workspace, cx) =
21995        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21996    let worktree_id = workspace.update(cx, |workspace, cx| {
21997        workspace.project().update(cx, |project, cx| {
21998            project.worktrees(cx).next().unwrap().read(cx).id()
21999        })
22000    });
22001
22002    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22003    let editor = workspace
22004        .update_in(cx, |workspace, window, cx| {
22005            workspace.open_path(
22006                (worktree_id, "main.rs"),
22007                Some(pane.downgrade()),
22008                true,
22009                window,
22010                cx,
22011            )
22012        })
22013        .unwrap()
22014        .await
22015        .downcast::<Editor>()
22016        .unwrap();
22017    pane.update(cx, |pane, cx| {
22018        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22019        open_editor.update(cx, |editor, cx| {
22020            assert_eq!(
22021                editor.display_text(cx),
22022                main_text,
22023                "Original main.rs text on initial open",
22024            );
22025        })
22026    });
22027    editor.update_in(cx, |editor, window, cx| {
22028        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22029    });
22030
22031    cx.update_global(|store: &mut SettingsStore, cx| {
22032        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22033            s.restore_on_file_reopen = Some(false);
22034        });
22035    });
22036    editor.update_in(cx, |editor, window, cx| {
22037        editor.fold_ranges(
22038            vec![
22039                Point::new(1, 0)..Point::new(1, 1),
22040                Point::new(2, 0)..Point::new(2, 2),
22041                Point::new(3, 0)..Point::new(3, 3),
22042            ],
22043            false,
22044            window,
22045            cx,
22046        );
22047    });
22048    pane.update_in(cx, |pane, window, cx| {
22049        pane.close_all_items(&CloseAllItems::default(), window, cx)
22050    })
22051    .await
22052    .unwrap();
22053    pane.update(cx, |pane, _| {
22054        assert!(pane.active_item().is_none());
22055    });
22056    cx.update_global(|store: &mut SettingsStore, cx| {
22057        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22058            s.restore_on_file_reopen = Some(true);
22059        });
22060    });
22061
22062    let _editor_reopened = workspace
22063        .update_in(cx, |workspace, window, cx| {
22064            workspace.open_path(
22065                (worktree_id, "main.rs"),
22066                Some(pane.downgrade()),
22067                true,
22068                window,
22069                cx,
22070            )
22071        })
22072        .unwrap()
22073        .await
22074        .downcast::<Editor>()
22075        .unwrap();
22076    pane.update(cx, |pane, cx| {
22077        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22078        open_editor.update(cx, |editor, cx| {
22079            assert_eq!(
22080                editor.display_text(cx),
22081                main_text,
22082                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22083            );
22084        })
22085    });
22086}
22087
22088#[gpui::test]
22089async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22090    struct EmptyModalView {
22091        focus_handle: gpui::FocusHandle,
22092    }
22093    impl EventEmitter<DismissEvent> for EmptyModalView {}
22094    impl Render for EmptyModalView {
22095        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22096            div()
22097        }
22098    }
22099    impl Focusable for EmptyModalView {
22100        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22101            self.focus_handle.clone()
22102        }
22103    }
22104    impl workspace::ModalView for EmptyModalView {}
22105    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22106        EmptyModalView {
22107            focus_handle: cx.focus_handle(),
22108        }
22109    }
22110
22111    init_test(cx, |_| {});
22112
22113    let fs = FakeFs::new(cx.executor());
22114    let project = Project::test(fs, [], cx).await;
22115    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22116    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22117    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22118    let editor = cx.new_window_entity(|window, cx| {
22119        Editor::new(
22120            EditorMode::full(),
22121            buffer,
22122            Some(project.clone()),
22123            window,
22124            cx,
22125        )
22126    });
22127    workspace
22128        .update(cx, |workspace, window, cx| {
22129            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22130        })
22131        .unwrap();
22132    editor.update_in(cx, |editor, window, cx| {
22133        editor.open_context_menu(&OpenContextMenu, window, cx);
22134        assert!(editor.mouse_context_menu.is_some());
22135    });
22136    workspace
22137        .update(cx, |workspace, window, cx| {
22138            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22139        })
22140        .unwrap();
22141    cx.read(|cx| {
22142        assert!(editor.read(cx).mouse_context_menu.is_none());
22143    });
22144}
22145
22146#[gpui::test]
22147async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22148    init_test(cx, |_| {});
22149
22150    let fs = FakeFs::new(cx.executor());
22151    fs.insert_file(path!("/file.html"), Default::default())
22152        .await;
22153
22154    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22155
22156    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22157    let html_language = Arc::new(Language::new(
22158        LanguageConfig {
22159            name: "HTML".into(),
22160            matcher: LanguageMatcher {
22161                path_suffixes: vec!["html".to_string()],
22162                ..LanguageMatcher::default()
22163            },
22164            brackets: BracketPairConfig {
22165                pairs: vec![BracketPair {
22166                    start: "<".into(),
22167                    end: ">".into(),
22168                    close: true,
22169                    ..Default::default()
22170                }],
22171                ..Default::default()
22172            },
22173            ..Default::default()
22174        },
22175        Some(tree_sitter_html::LANGUAGE.into()),
22176    ));
22177    language_registry.add(html_language);
22178    let mut fake_servers = language_registry.register_fake_lsp(
22179        "HTML",
22180        FakeLspAdapter {
22181            capabilities: lsp::ServerCapabilities {
22182                completion_provider: Some(lsp::CompletionOptions {
22183                    resolve_provider: Some(true),
22184                    ..Default::default()
22185                }),
22186                ..Default::default()
22187            },
22188            ..Default::default()
22189        },
22190    );
22191
22192    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22193    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22194
22195    let worktree_id = workspace
22196        .update(cx, |workspace, _window, cx| {
22197            workspace.project().update(cx, |project, cx| {
22198                project.worktrees(cx).next().unwrap().read(cx).id()
22199            })
22200        })
22201        .unwrap();
22202    project
22203        .update(cx, |project, cx| {
22204            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22205        })
22206        .await
22207        .unwrap();
22208    let editor = workspace
22209        .update(cx, |workspace, window, cx| {
22210            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22211        })
22212        .unwrap()
22213        .await
22214        .unwrap()
22215        .downcast::<Editor>()
22216        .unwrap();
22217
22218    let fake_server = fake_servers.next().await.unwrap();
22219    editor.update_in(cx, |editor, window, cx| {
22220        editor.set_text("<ad></ad>", window, cx);
22221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22222            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22223        });
22224        let Some((buffer, _)) = editor
22225            .buffer
22226            .read(cx)
22227            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22228        else {
22229            panic!("Failed to get buffer for selection position");
22230        };
22231        let buffer = buffer.read(cx);
22232        let buffer_id = buffer.remote_id();
22233        let opening_range =
22234            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22235        let closing_range =
22236            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22237        let mut linked_ranges = HashMap::default();
22238        linked_ranges.insert(
22239            buffer_id,
22240            vec![(opening_range.clone(), vec![closing_range.clone()])],
22241        );
22242        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22243    });
22244    let mut completion_handle =
22245        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22246            Ok(Some(lsp::CompletionResponse::Array(vec![
22247                lsp::CompletionItem {
22248                    label: "head".to_string(),
22249                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22250                        lsp::InsertReplaceEdit {
22251                            new_text: "head".to_string(),
22252                            insert: lsp::Range::new(
22253                                lsp::Position::new(0, 1),
22254                                lsp::Position::new(0, 3),
22255                            ),
22256                            replace: lsp::Range::new(
22257                                lsp::Position::new(0, 1),
22258                                lsp::Position::new(0, 3),
22259                            ),
22260                        },
22261                    )),
22262                    ..Default::default()
22263                },
22264            ])))
22265        });
22266    editor.update_in(cx, |editor, window, cx| {
22267        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22268    });
22269    cx.run_until_parked();
22270    completion_handle.next().await.unwrap();
22271    editor.update(cx, |editor, _| {
22272        assert!(
22273            editor.context_menu_visible(),
22274            "Completion menu should be visible"
22275        );
22276    });
22277    editor.update_in(cx, |editor, window, cx| {
22278        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22279    });
22280    cx.executor().run_until_parked();
22281    editor.update(cx, |editor, cx| {
22282        assert_eq!(editor.text(cx), "<head></head>");
22283    });
22284}
22285
22286#[gpui::test]
22287async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22288    init_test(cx, |_| {});
22289
22290    let fs = FakeFs::new(cx.executor());
22291    fs.insert_tree(
22292        path!("/root"),
22293        json!({
22294            "a": {
22295                "main.rs": "fn main() {}",
22296            },
22297            "foo": {
22298                "bar": {
22299                    "external_file.rs": "pub mod external {}",
22300                }
22301            }
22302        }),
22303    )
22304    .await;
22305
22306    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22307    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22308    language_registry.add(rust_lang());
22309    let _fake_servers = language_registry.register_fake_lsp(
22310        "Rust",
22311        FakeLspAdapter {
22312            ..FakeLspAdapter::default()
22313        },
22314    );
22315    let (workspace, cx) =
22316        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22317    let worktree_id = workspace.update(cx, |workspace, cx| {
22318        workspace.project().update(cx, |project, cx| {
22319            project.worktrees(cx).next().unwrap().read(cx).id()
22320        })
22321    });
22322
22323    let assert_language_servers_count =
22324        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22325            project.update(cx, |project, cx| {
22326                let current = project
22327                    .lsp_store()
22328                    .read(cx)
22329                    .as_local()
22330                    .unwrap()
22331                    .language_servers
22332                    .len();
22333                assert_eq!(expected, current, "{context}");
22334            });
22335        };
22336
22337    assert_language_servers_count(
22338        0,
22339        "No servers should be running before any file is open",
22340        cx,
22341    );
22342    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22343    let main_editor = workspace
22344        .update_in(cx, |workspace, window, cx| {
22345            workspace.open_path(
22346                (worktree_id, "main.rs"),
22347                Some(pane.downgrade()),
22348                true,
22349                window,
22350                cx,
22351            )
22352        })
22353        .unwrap()
22354        .await
22355        .downcast::<Editor>()
22356        .unwrap();
22357    pane.update(cx, |pane, cx| {
22358        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22359        open_editor.update(cx, |editor, cx| {
22360            assert_eq!(
22361                editor.display_text(cx),
22362                "fn main() {}",
22363                "Original main.rs text on initial open",
22364            );
22365        });
22366        assert_eq!(open_editor, main_editor);
22367    });
22368    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22369
22370    let external_editor = workspace
22371        .update_in(cx, |workspace, window, cx| {
22372            workspace.open_abs_path(
22373                PathBuf::from("/root/foo/bar/external_file.rs"),
22374                OpenOptions::default(),
22375                window,
22376                cx,
22377            )
22378        })
22379        .await
22380        .expect("opening external file")
22381        .downcast::<Editor>()
22382        .expect("downcasted external file's open element to editor");
22383    pane.update(cx, |pane, cx| {
22384        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22385        open_editor.update(cx, |editor, cx| {
22386            assert_eq!(
22387                editor.display_text(cx),
22388                "pub mod external {}",
22389                "External file is open now",
22390            );
22391        });
22392        assert_eq!(open_editor, external_editor);
22393    });
22394    assert_language_servers_count(
22395        1,
22396        "Second, external, *.rs file should join the existing server",
22397        cx,
22398    );
22399
22400    pane.update_in(cx, |pane, window, cx| {
22401        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22402    })
22403    .await
22404    .unwrap();
22405    pane.update_in(cx, |pane, window, cx| {
22406        pane.navigate_backward(window, cx);
22407    });
22408    cx.run_until_parked();
22409    pane.update(cx, |pane, cx| {
22410        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22411        open_editor.update(cx, |editor, cx| {
22412            assert_eq!(
22413                editor.display_text(cx),
22414                "pub mod external {}",
22415                "External file is open now",
22416            );
22417        });
22418    });
22419    assert_language_servers_count(
22420        1,
22421        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22422        cx,
22423    );
22424
22425    cx.update(|_, cx| {
22426        workspace::reload(&workspace::Reload::default(), cx);
22427    });
22428    assert_language_servers_count(
22429        1,
22430        "After reloading the worktree with local and external files opened, only one project should be started",
22431        cx,
22432    );
22433}
22434
22435#[gpui::test]
22436async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22437    init_test(cx, |_| {});
22438
22439    let mut cx = EditorTestContext::new(cx).await;
22440    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22441    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22442
22443    // test cursor move to start of each line on tab
22444    // for `if`, `elif`, `else`, `while`, `with` and `for`
22445    cx.set_state(indoc! {"
22446        def main():
22447        ˇ    for item in items:
22448        ˇ        while item.active:
22449        ˇ            if item.value > 10:
22450        ˇ                continue
22451        ˇ            elif item.value < 0:
22452        ˇ                break
22453        ˇ            else:
22454        ˇ                with item.context() as ctx:
22455        ˇ                    yield count
22456        ˇ        else:
22457        ˇ            log('while else')
22458        ˇ    else:
22459        ˇ        log('for else')
22460    "});
22461    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22462    cx.assert_editor_state(indoc! {"
22463        def main():
22464            ˇfor item in items:
22465                ˇwhile item.active:
22466                    ˇif item.value > 10:
22467                        ˇcontinue
22468                    ˇelif item.value < 0:
22469                        ˇbreak
22470                    ˇelse:
22471                        ˇwith item.context() as ctx:
22472                            ˇyield count
22473                ˇelse:
22474                    ˇlog('while else')
22475            ˇelse:
22476                ˇlog('for else')
22477    "});
22478    // test relative indent is preserved when tab
22479    // for `if`, `elif`, `else`, `while`, `with` and `for`
22480    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22481    cx.assert_editor_state(indoc! {"
22482        def main():
22483                ˇfor item in items:
22484                    ˇwhile item.active:
22485                        ˇif item.value > 10:
22486                            ˇcontinue
22487                        ˇelif item.value < 0:
22488                            ˇbreak
22489                        ˇelse:
22490                            ˇwith item.context() as ctx:
22491                                ˇyield count
22492                    ˇelse:
22493                        ˇlog('while else')
22494                ˇelse:
22495                    ˇlog('for else')
22496    "});
22497
22498    // test cursor move to start of each line on tab
22499    // for `try`, `except`, `else`, `finally`, `match` and `def`
22500    cx.set_state(indoc! {"
22501        def main():
22502        ˇ    try:
22503        ˇ        fetch()
22504        ˇ    except ValueError:
22505        ˇ        handle_error()
22506        ˇ    else:
22507        ˇ        match value:
22508        ˇ            case _:
22509        ˇ    finally:
22510        ˇ        def status():
22511        ˇ            return 0
22512    "});
22513    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22514    cx.assert_editor_state(indoc! {"
22515        def main():
22516            ˇtry:
22517                ˇfetch()
22518            ˇexcept ValueError:
22519                ˇhandle_error()
22520            ˇelse:
22521                ˇmatch value:
22522                    ˇcase _:
22523            ˇfinally:
22524                ˇdef status():
22525                    ˇreturn 0
22526    "});
22527    // test relative indent is preserved when tab
22528    // for `try`, `except`, `else`, `finally`, `match` and `def`
22529    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22530    cx.assert_editor_state(indoc! {"
22531        def main():
22532                ˇtry:
22533                    ˇfetch()
22534                ˇexcept ValueError:
22535                    ˇhandle_error()
22536                ˇelse:
22537                    ˇmatch value:
22538                        ˇcase _:
22539                ˇfinally:
22540                    ˇdef status():
22541                        ˇreturn 0
22542    "});
22543}
22544
22545#[gpui::test]
22546async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22547    init_test(cx, |_| {});
22548
22549    let mut cx = EditorTestContext::new(cx).await;
22550    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22551    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22552
22553    // test `else` auto outdents when typed inside `if` block
22554    cx.set_state(indoc! {"
22555        def main():
22556            if i == 2:
22557                return
22558                ˇ
22559    "});
22560    cx.update_editor(|editor, window, cx| {
22561        editor.handle_input("else:", window, cx);
22562    });
22563    cx.assert_editor_state(indoc! {"
22564        def main():
22565            if i == 2:
22566                return
22567            else:ˇ
22568    "});
22569
22570    // test `except` auto outdents when typed inside `try` block
22571    cx.set_state(indoc! {"
22572        def main():
22573            try:
22574                i = 2
22575                ˇ
22576    "});
22577    cx.update_editor(|editor, window, cx| {
22578        editor.handle_input("except:", window, cx);
22579    });
22580    cx.assert_editor_state(indoc! {"
22581        def main():
22582            try:
22583                i = 2
22584            except:ˇ
22585    "});
22586
22587    // test `else` auto outdents when typed inside `except` block
22588    cx.set_state(indoc! {"
22589        def main():
22590            try:
22591                i = 2
22592            except:
22593                j = 2
22594                ˇ
22595    "});
22596    cx.update_editor(|editor, window, cx| {
22597        editor.handle_input("else:", window, cx);
22598    });
22599    cx.assert_editor_state(indoc! {"
22600        def main():
22601            try:
22602                i = 2
22603            except:
22604                j = 2
22605            else:ˇ
22606    "});
22607
22608    // test `finally` auto outdents when typed inside `else` block
22609    cx.set_state(indoc! {"
22610        def main():
22611            try:
22612                i = 2
22613            except:
22614                j = 2
22615            else:
22616                k = 2
22617                ˇ
22618    "});
22619    cx.update_editor(|editor, window, cx| {
22620        editor.handle_input("finally:", window, cx);
22621    });
22622    cx.assert_editor_state(indoc! {"
22623        def main():
22624            try:
22625                i = 2
22626            except:
22627                j = 2
22628            else:
22629                k = 2
22630            finally:ˇ
22631    "});
22632
22633    // test `else` does not outdents when typed inside `except` block right after for block
22634    cx.set_state(indoc! {"
22635        def main():
22636            try:
22637                i = 2
22638            except:
22639                for i in range(n):
22640                    pass
22641                ˇ
22642    "});
22643    cx.update_editor(|editor, window, cx| {
22644        editor.handle_input("else:", window, cx);
22645    });
22646    cx.assert_editor_state(indoc! {"
22647        def main():
22648            try:
22649                i = 2
22650            except:
22651                for i in range(n):
22652                    pass
22653                else:ˇ
22654    "});
22655
22656    // test `finally` auto outdents when typed inside `else` block right after for block
22657    cx.set_state(indoc! {"
22658        def main():
22659            try:
22660                i = 2
22661            except:
22662                j = 2
22663            else:
22664                for i in range(n):
22665                    pass
22666                ˇ
22667    "});
22668    cx.update_editor(|editor, window, cx| {
22669        editor.handle_input("finally:", window, cx);
22670    });
22671    cx.assert_editor_state(indoc! {"
22672        def main():
22673            try:
22674                i = 2
22675            except:
22676                j = 2
22677            else:
22678                for i in range(n):
22679                    pass
22680            finally:ˇ
22681    "});
22682
22683    // test `except` outdents to inner "try" block
22684    cx.set_state(indoc! {"
22685        def main():
22686            try:
22687                i = 2
22688                if i == 2:
22689                    try:
22690                        i = 3
22691                        ˇ
22692    "});
22693    cx.update_editor(|editor, window, cx| {
22694        editor.handle_input("except:", window, cx);
22695    });
22696    cx.assert_editor_state(indoc! {"
22697        def main():
22698            try:
22699                i = 2
22700                if i == 2:
22701                    try:
22702                        i = 3
22703                    except:ˇ
22704    "});
22705
22706    // test `except` outdents to outer "try" block
22707    cx.set_state(indoc! {"
22708        def main():
22709            try:
22710                i = 2
22711                if i == 2:
22712                    try:
22713                        i = 3
22714                ˇ
22715    "});
22716    cx.update_editor(|editor, window, cx| {
22717        editor.handle_input("except:", window, cx);
22718    });
22719    cx.assert_editor_state(indoc! {"
22720        def main():
22721            try:
22722                i = 2
22723                if i == 2:
22724                    try:
22725                        i = 3
22726            except:ˇ
22727    "});
22728
22729    // test `else` stays at correct indent when typed after `for` block
22730    cx.set_state(indoc! {"
22731        def main():
22732            for i in range(10):
22733                if i == 3:
22734                    break
22735            ˇ
22736    "});
22737    cx.update_editor(|editor, window, cx| {
22738        editor.handle_input("else:", window, cx);
22739    });
22740    cx.assert_editor_state(indoc! {"
22741        def main():
22742            for i in range(10):
22743                if i == 3:
22744                    break
22745            else:ˇ
22746    "});
22747
22748    // test does not outdent on typing after line with square brackets
22749    cx.set_state(indoc! {"
22750        def f() -> list[str]:
22751            ˇ
22752    "});
22753    cx.update_editor(|editor, window, cx| {
22754        editor.handle_input("a", window, cx);
22755    });
22756    cx.assert_editor_state(indoc! {"
22757        def f() -> list[str]:
2275822759    "});
22760
22761    // test does not outdent on typing : after case keyword
22762    cx.set_state(indoc! {"
22763        match 1:
22764            caseˇ
22765    "});
22766    cx.update_editor(|editor, window, cx| {
22767        editor.handle_input(":", window, cx);
22768    });
22769    cx.assert_editor_state(indoc! {"
22770        match 1:
22771            case:ˇ
22772    "});
22773}
22774
22775#[gpui::test]
22776async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22777    init_test(cx, |_| {});
22778    update_test_language_settings(cx, |settings| {
22779        settings.defaults.extend_comment_on_newline = Some(false);
22780    });
22781    let mut cx = EditorTestContext::new(cx).await;
22782    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22783    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22784
22785    // test correct indent after newline on comment
22786    cx.set_state(indoc! {"
22787        # COMMENT:ˇ
22788    "});
22789    cx.update_editor(|editor, window, cx| {
22790        editor.newline(&Newline, window, cx);
22791    });
22792    cx.assert_editor_state(indoc! {"
22793        # COMMENT:
22794        ˇ
22795    "});
22796
22797    // test correct indent after newline in brackets
22798    cx.set_state(indoc! {"
22799        {ˇ}
22800    "});
22801    cx.update_editor(|editor, window, cx| {
22802        editor.newline(&Newline, window, cx);
22803    });
22804    cx.run_until_parked();
22805    cx.assert_editor_state(indoc! {"
22806        {
22807            ˇ
22808        }
22809    "});
22810
22811    cx.set_state(indoc! {"
22812        (ˇ)
22813    "});
22814    cx.update_editor(|editor, window, cx| {
22815        editor.newline(&Newline, window, cx);
22816    });
22817    cx.run_until_parked();
22818    cx.assert_editor_state(indoc! {"
22819        (
22820            ˇ
22821        )
22822    "});
22823
22824    // do not indent after empty lists or dictionaries
22825    cx.set_state(indoc! {"
22826        a = []ˇ
22827    "});
22828    cx.update_editor(|editor, window, cx| {
22829        editor.newline(&Newline, window, cx);
22830    });
22831    cx.run_until_parked();
22832    cx.assert_editor_state(indoc! {"
22833        a = []
22834        ˇ
22835    "});
22836}
22837
22838#[gpui::test]
22839async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22840    init_test(cx, |_| {});
22841
22842    let mut cx = EditorTestContext::new(cx).await;
22843    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22844    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22845
22846    // test cursor move to start of each line on tab
22847    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22848    cx.set_state(indoc! {"
22849        function main() {
22850        ˇ    for item in $items; do
22851        ˇ        while [ -n \"$item\" ]; do
22852        ˇ            if [ \"$value\" -gt 10 ]; then
22853        ˇ                continue
22854        ˇ            elif [ \"$value\" -lt 0 ]; then
22855        ˇ                break
22856        ˇ            else
22857        ˇ                echo \"$item\"
22858        ˇ            fi
22859        ˇ        done
22860        ˇ    done
22861        ˇ}
22862    "});
22863    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22864    cx.assert_editor_state(indoc! {"
22865        function main() {
22866            ˇfor item in $items; do
22867                ˇwhile [ -n \"$item\" ]; do
22868                    ˇif [ \"$value\" -gt 10 ]; then
22869                        ˇcontinue
22870                    ˇelif [ \"$value\" -lt 0 ]; then
22871                        ˇbreak
22872                    ˇelse
22873                        ˇecho \"$item\"
22874                    ˇfi
22875                ˇdone
22876            ˇdone
22877        ˇ}
22878    "});
22879    // test relative indent is preserved when tab
22880    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22881    cx.assert_editor_state(indoc! {"
22882        function main() {
22883                ˇfor item in $items; do
22884                    ˇwhile [ -n \"$item\" ]; do
22885                        ˇif [ \"$value\" -gt 10 ]; then
22886                            ˇcontinue
22887                        ˇelif [ \"$value\" -lt 0 ]; then
22888                            ˇbreak
22889                        ˇelse
22890                            ˇecho \"$item\"
22891                        ˇfi
22892                    ˇdone
22893                ˇdone
22894            ˇ}
22895    "});
22896
22897    // test cursor move to start of each line on tab
22898    // for `case` statement with patterns
22899    cx.set_state(indoc! {"
22900        function handle() {
22901        ˇ    case \"$1\" in
22902        ˇ        start)
22903        ˇ            echo \"a\"
22904        ˇ            ;;
22905        ˇ        stop)
22906        ˇ            echo \"b\"
22907        ˇ            ;;
22908        ˇ        *)
22909        ˇ            echo \"c\"
22910        ˇ            ;;
22911        ˇ    esac
22912        ˇ}
22913    "});
22914    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22915    cx.assert_editor_state(indoc! {"
22916        function handle() {
22917            ˇcase \"$1\" in
22918                ˇstart)
22919                    ˇecho \"a\"
22920                    ˇ;;
22921                ˇstop)
22922                    ˇecho \"b\"
22923                    ˇ;;
22924                ˇ*)
22925                    ˇecho \"c\"
22926                    ˇ;;
22927            ˇesac
22928        ˇ}
22929    "});
22930}
22931
22932#[gpui::test]
22933async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
22934    init_test(cx, |_| {});
22935
22936    let mut cx = EditorTestContext::new(cx).await;
22937    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22938    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22939
22940    // test indents on comment insert
22941    cx.set_state(indoc! {"
22942        function main() {
22943        ˇ    for item in $items; do
22944        ˇ        while [ -n \"$item\" ]; do
22945        ˇ            if [ \"$value\" -gt 10 ]; then
22946        ˇ                continue
22947        ˇ            elif [ \"$value\" -lt 0 ]; then
22948        ˇ                break
22949        ˇ            else
22950        ˇ                echo \"$item\"
22951        ˇ            fi
22952        ˇ        done
22953        ˇ    done
22954        ˇ}
22955    "});
22956    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
22957    cx.assert_editor_state(indoc! {"
22958        function main() {
22959        #ˇ    for item in $items; do
22960        #ˇ        while [ -n \"$item\" ]; do
22961        #ˇ            if [ \"$value\" -gt 10 ]; then
22962        #ˇ                continue
22963        #ˇ            elif [ \"$value\" -lt 0 ]; then
22964        #ˇ                break
22965        #ˇ            else
22966        #ˇ                echo \"$item\"
22967        #ˇ            fi
22968        #ˇ        done
22969        #ˇ    done
22970        #ˇ}
22971    "});
22972}
22973
22974#[gpui::test]
22975async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
22976    init_test(cx, |_| {});
22977
22978    let mut cx = EditorTestContext::new(cx).await;
22979    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22980    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22981
22982    // test `else` auto outdents when typed inside `if` block
22983    cx.set_state(indoc! {"
22984        if [ \"$1\" = \"test\" ]; then
22985            echo \"foo bar\"
22986            ˇ
22987    "});
22988    cx.update_editor(|editor, window, cx| {
22989        editor.handle_input("else", window, cx);
22990    });
22991    cx.assert_editor_state(indoc! {"
22992        if [ \"$1\" = \"test\" ]; then
22993            echo \"foo bar\"
22994        elseˇ
22995    "});
22996
22997    // test `elif` auto outdents when typed inside `if` block
22998    cx.set_state(indoc! {"
22999        if [ \"$1\" = \"test\" ]; then
23000            echo \"foo bar\"
23001            ˇ
23002    "});
23003    cx.update_editor(|editor, window, cx| {
23004        editor.handle_input("elif", window, cx);
23005    });
23006    cx.assert_editor_state(indoc! {"
23007        if [ \"$1\" = \"test\" ]; then
23008            echo \"foo bar\"
23009        elifˇ
23010    "});
23011
23012    // test `fi` auto outdents when typed inside `else` block
23013    cx.set_state(indoc! {"
23014        if [ \"$1\" = \"test\" ]; then
23015            echo \"foo bar\"
23016        else
23017            echo \"bar baz\"
23018            ˇ
23019    "});
23020    cx.update_editor(|editor, window, cx| {
23021        editor.handle_input("fi", window, cx);
23022    });
23023    cx.assert_editor_state(indoc! {"
23024        if [ \"$1\" = \"test\" ]; then
23025            echo \"foo bar\"
23026        else
23027            echo \"bar baz\"
23028        fiˇ
23029    "});
23030
23031    // test `done` auto outdents when typed inside `while` block
23032    cx.set_state(indoc! {"
23033        while read line; do
23034            echo \"$line\"
23035            ˇ
23036    "});
23037    cx.update_editor(|editor, window, cx| {
23038        editor.handle_input("done", window, cx);
23039    });
23040    cx.assert_editor_state(indoc! {"
23041        while read line; do
23042            echo \"$line\"
23043        doneˇ
23044    "});
23045
23046    // test `done` auto outdents when typed inside `for` block
23047    cx.set_state(indoc! {"
23048        for file in *.txt; do
23049            cat \"$file\"
23050            ˇ
23051    "});
23052    cx.update_editor(|editor, window, cx| {
23053        editor.handle_input("done", window, cx);
23054    });
23055    cx.assert_editor_state(indoc! {"
23056        for file in *.txt; do
23057            cat \"$file\"
23058        doneˇ
23059    "});
23060
23061    // test `esac` auto outdents when typed inside `case` block
23062    cx.set_state(indoc! {"
23063        case \"$1\" in
23064            start)
23065                echo \"foo bar\"
23066                ;;
23067            stop)
23068                echo \"bar baz\"
23069                ;;
23070            ˇ
23071    "});
23072    cx.update_editor(|editor, window, cx| {
23073        editor.handle_input("esac", window, cx);
23074    });
23075    cx.assert_editor_state(indoc! {"
23076        case \"$1\" in
23077            start)
23078                echo \"foo bar\"
23079                ;;
23080            stop)
23081                echo \"bar baz\"
23082                ;;
23083        esacˇ
23084    "});
23085
23086    // test `*)` auto outdents when typed inside `case` block
23087    cx.set_state(indoc! {"
23088        case \"$1\" in
23089            start)
23090                echo \"foo bar\"
23091                ;;
23092                ˇ
23093    "});
23094    cx.update_editor(|editor, window, cx| {
23095        editor.handle_input("*)", window, cx);
23096    });
23097    cx.assert_editor_state(indoc! {"
23098        case \"$1\" in
23099            start)
23100                echo \"foo bar\"
23101                ;;
23102            *)ˇ
23103    "});
23104
23105    // test `fi` outdents to correct level with nested if blocks
23106    cx.set_state(indoc! {"
23107        if [ \"$1\" = \"test\" ]; then
23108            echo \"outer if\"
23109            if [ \"$2\" = \"debug\" ]; then
23110                echo \"inner if\"
23111                ˇ
23112    "});
23113    cx.update_editor(|editor, window, cx| {
23114        editor.handle_input("fi", window, cx);
23115    });
23116    cx.assert_editor_state(indoc! {"
23117        if [ \"$1\" = \"test\" ]; then
23118            echo \"outer if\"
23119            if [ \"$2\" = \"debug\" ]; then
23120                echo \"inner if\"
23121            fiˇ
23122    "});
23123}
23124
23125#[gpui::test]
23126async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23127    init_test(cx, |_| {});
23128    update_test_language_settings(cx, |settings| {
23129        settings.defaults.extend_comment_on_newline = Some(false);
23130    });
23131    let mut cx = EditorTestContext::new(cx).await;
23132    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23133    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23134
23135    // test correct indent after newline on comment
23136    cx.set_state(indoc! {"
23137        # COMMENT:ˇ
23138    "});
23139    cx.update_editor(|editor, window, cx| {
23140        editor.newline(&Newline, window, cx);
23141    });
23142    cx.assert_editor_state(indoc! {"
23143        # COMMENT:
23144        ˇ
23145    "});
23146
23147    // test correct indent after newline after `then`
23148    cx.set_state(indoc! {"
23149
23150        if [ \"$1\" = \"test\" ]; thenˇ
23151    "});
23152    cx.update_editor(|editor, window, cx| {
23153        editor.newline(&Newline, window, cx);
23154    });
23155    cx.run_until_parked();
23156    cx.assert_editor_state(indoc! {"
23157
23158        if [ \"$1\" = \"test\" ]; then
23159            ˇ
23160    "});
23161
23162    // test correct indent after newline after `else`
23163    cx.set_state(indoc! {"
23164        if [ \"$1\" = \"test\" ]; then
23165        elseˇ
23166    "});
23167    cx.update_editor(|editor, window, cx| {
23168        editor.newline(&Newline, window, cx);
23169    });
23170    cx.run_until_parked();
23171    cx.assert_editor_state(indoc! {"
23172        if [ \"$1\" = \"test\" ]; then
23173        else
23174            ˇ
23175    "});
23176
23177    // test correct indent after newline after `elif`
23178    cx.set_state(indoc! {"
23179        if [ \"$1\" = \"test\" ]; then
23180        elifˇ
23181    "});
23182    cx.update_editor(|editor, window, cx| {
23183        editor.newline(&Newline, window, cx);
23184    });
23185    cx.run_until_parked();
23186    cx.assert_editor_state(indoc! {"
23187        if [ \"$1\" = \"test\" ]; then
23188        elif
23189            ˇ
23190    "});
23191
23192    // test correct indent after newline after `do`
23193    cx.set_state(indoc! {"
23194        for file in *.txt; doˇ
23195    "});
23196    cx.update_editor(|editor, window, cx| {
23197        editor.newline(&Newline, window, cx);
23198    });
23199    cx.run_until_parked();
23200    cx.assert_editor_state(indoc! {"
23201        for file in *.txt; do
23202            ˇ
23203    "});
23204
23205    // test correct indent after newline after case pattern
23206    cx.set_state(indoc! {"
23207        case \"$1\" in
23208            start)ˇ
23209    "});
23210    cx.update_editor(|editor, window, cx| {
23211        editor.newline(&Newline, window, cx);
23212    });
23213    cx.run_until_parked();
23214    cx.assert_editor_state(indoc! {"
23215        case \"$1\" in
23216            start)
23217                ˇ
23218    "});
23219
23220    // test correct indent after newline after case pattern
23221    cx.set_state(indoc! {"
23222        case \"$1\" in
23223            start)
23224                ;;
23225            *)ˇ
23226    "});
23227    cx.update_editor(|editor, window, cx| {
23228        editor.newline(&Newline, window, cx);
23229    });
23230    cx.run_until_parked();
23231    cx.assert_editor_state(indoc! {"
23232        case \"$1\" in
23233            start)
23234                ;;
23235            *)
23236                ˇ
23237    "});
23238
23239    // test correct indent after newline after function opening brace
23240    cx.set_state(indoc! {"
23241        function test() {ˇ}
23242    "});
23243    cx.update_editor(|editor, window, cx| {
23244        editor.newline(&Newline, window, cx);
23245    });
23246    cx.run_until_parked();
23247    cx.assert_editor_state(indoc! {"
23248        function test() {
23249            ˇ
23250        }
23251    "});
23252
23253    // test no extra indent after semicolon on same line
23254    cx.set_state(indoc! {"
23255        echo \"test\"23256    "});
23257    cx.update_editor(|editor, window, cx| {
23258        editor.newline(&Newline, window, cx);
23259    });
23260    cx.run_until_parked();
23261    cx.assert_editor_state(indoc! {"
23262        echo \"test\";
23263        ˇ
23264    "});
23265}
23266
23267fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23268    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23269    point..point
23270}
23271
23272#[track_caller]
23273fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23274    let (text, ranges) = marked_text_ranges(marked_text, true);
23275    assert_eq!(editor.text(cx), text);
23276    assert_eq!(
23277        editor.selections.ranges(cx),
23278        ranges,
23279        "Assert selections are {}",
23280        marked_text
23281    );
23282}
23283
23284pub fn handle_signature_help_request(
23285    cx: &mut EditorLspTestContext,
23286    mocked_response: lsp::SignatureHelp,
23287) -> impl Future<Output = ()> + use<> {
23288    let mut request =
23289        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23290            let mocked_response = mocked_response.clone();
23291            async move { Ok(Some(mocked_response)) }
23292        });
23293
23294    async move {
23295        request.next().await;
23296    }
23297}
23298
23299#[track_caller]
23300pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23301    cx.update_editor(|editor, _, _| {
23302        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23303            let entries = menu.entries.borrow();
23304            let entries = entries
23305                .iter()
23306                .map(|entry| entry.string.as_str())
23307                .collect::<Vec<_>>();
23308            assert_eq!(entries, expected);
23309        } else {
23310            panic!("Expected completions menu");
23311        }
23312    });
23313}
23314
23315/// Handle completion request passing a marked string specifying where the completion
23316/// should be triggered from using '|' character, what range should be replaced, and what completions
23317/// should be returned using '<' and '>' to delimit the range.
23318///
23319/// Also see `handle_completion_request_with_insert_and_replace`.
23320#[track_caller]
23321pub fn handle_completion_request(
23322    marked_string: &str,
23323    completions: Vec<&'static str>,
23324    is_incomplete: bool,
23325    counter: Arc<AtomicUsize>,
23326    cx: &mut EditorLspTestContext,
23327) -> impl Future<Output = ()> {
23328    let complete_from_marker: TextRangeMarker = '|'.into();
23329    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23330    let (_, mut marked_ranges) = marked_text_ranges_by(
23331        marked_string,
23332        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23333    );
23334
23335    let complete_from_position =
23336        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23337    let replace_range =
23338        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23339
23340    let mut request =
23341        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23342            let completions = completions.clone();
23343            counter.fetch_add(1, atomic::Ordering::Release);
23344            async move {
23345                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23346                assert_eq!(
23347                    params.text_document_position.position,
23348                    complete_from_position
23349                );
23350                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23351                    is_incomplete: is_incomplete,
23352                    item_defaults: None,
23353                    items: completions
23354                        .iter()
23355                        .map(|completion_text| lsp::CompletionItem {
23356                            label: completion_text.to_string(),
23357                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23358                                range: replace_range,
23359                                new_text: completion_text.to_string(),
23360                            })),
23361                            ..Default::default()
23362                        })
23363                        .collect(),
23364                })))
23365            }
23366        });
23367
23368    async move {
23369        request.next().await;
23370    }
23371}
23372
23373/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23374/// given instead, which also contains an `insert` range.
23375///
23376/// This function uses markers to define ranges:
23377/// - `|` marks the cursor position
23378/// - `<>` marks the replace range
23379/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23380pub fn handle_completion_request_with_insert_and_replace(
23381    cx: &mut EditorLspTestContext,
23382    marked_string: &str,
23383    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23384    counter: Arc<AtomicUsize>,
23385) -> impl Future<Output = ()> {
23386    let complete_from_marker: TextRangeMarker = '|'.into();
23387    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23388    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23389
23390    let (_, mut marked_ranges) = marked_text_ranges_by(
23391        marked_string,
23392        vec![
23393            complete_from_marker.clone(),
23394            replace_range_marker.clone(),
23395            insert_range_marker.clone(),
23396        ],
23397    );
23398
23399    let complete_from_position =
23400        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23401    let replace_range =
23402        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23403
23404    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23405        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23406        _ => lsp::Range {
23407            start: replace_range.start,
23408            end: complete_from_position,
23409        },
23410    };
23411
23412    let mut request =
23413        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23414            let completions = completions.clone();
23415            counter.fetch_add(1, atomic::Ordering::Release);
23416            async move {
23417                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23418                assert_eq!(
23419                    params.text_document_position.position, complete_from_position,
23420                    "marker `|` position doesn't match",
23421                );
23422                Ok(Some(lsp::CompletionResponse::Array(
23423                    completions
23424                        .iter()
23425                        .map(|(label, new_text)| lsp::CompletionItem {
23426                            label: label.to_string(),
23427                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23428                                lsp::InsertReplaceEdit {
23429                                    insert: insert_range,
23430                                    replace: replace_range,
23431                                    new_text: new_text.to_string(),
23432                                },
23433                            )),
23434                            ..Default::default()
23435                        })
23436                        .collect(),
23437                )))
23438            }
23439        });
23440
23441    async move {
23442        request.next().await;
23443    }
23444}
23445
23446fn handle_resolve_completion_request(
23447    cx: &mut EditorLspTestContext,
23448    edits: Option<Vec<(&'static str, &'static str)>>,
23449) -> impl Future<Output = ()> {
23450    let edits = edits.map(|edits| {
23451        edits
23452            .iter()
23453            .map(|(marked_string, new_text)| {
23454                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23455                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23456                lsp::TextEdit::new(replace_range, new_text.to_string())
23457            })
23458            .collect::<Vec<_>>()
23459    });
23460
23461    let mut request =
23462        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23463            let edits = edits.clone();
23464            async move {
23465                Ok(lsp::CompletionItem {
23466                    additional_text_edits: edits,
23467                    ..Default::default()
23468                })
23469            }
23470        });
23471
23472    async move {
23473        request.next().await;
23474    }
23475}
23476
23477pub(crate) fn update_test_language_settings(
23478    cx: &mut TestAppContext,
23479    f: impl Fn(&mut AllLanguageSettingsContent),
23480) {
23481    cx.update(|cx| {
23482        SettingsStore::update_global(cx, |store, cx| {
23483            store.update_user_settings::<AllLanguageSettings>(cx, f);
23484        });
23485    });
23486}
23487
23488pub(crate) fn update_test_project_settings(
23489    cx: &mut TestAppContext,
23490    f: impl Fn(&mut ProjectSettings),
23491) {
23492    cx.update(|cx| {
23493        SettingsStore::update_global(cx, |store, cx| {
23494            store.update_user_settings::<ProjectSettings>(cx, f);
23495        });
23496    });
23497}
23498
23499pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23500    cx.update(|cx| {
23501        assets::Assets.load_test_fonts(cx);
23502        let store = SettingsStore::test(cx);
23503        cx.set_global(store);
23504        theme::init(theme::LoadThemes::JustBase, cx);
23505        release_channel::init(SemanticVersion::default(), cx);
23506        client::init_settings(cx);
23507        language::init(cx);
23508        Project::init_settings(cx);
23509        workspace::init_settings(cx);
23510        crate::init(cx);
23511    });
23512    zlog::init_test();
23513    update_test_language_settings(cx, f);
23514}
23515
23516#[track_caller]
23517fn assert_hunk_revert(
23518    not_reverted_text_with_selections: &str,
23519    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23520    expected_reverted_text_with_selections: &str,
23521    base_text: &str,
23522    cx: &mut EditorLspTestContext,
23523) {
23524    cx.set_state(not_reverted_text_with_selections);
23525    cx.set_head_text(base_text);
23526    cx.executor().run_until_parked();
23527
23528    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23529        let snapshot = editor.snapshot(window, cx);
23530        let reverted_hunk_statuses = snapshot
23531            .buffer_snapshot
23532            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23533            .map(|hunk| hunk.status().kind)
23534            .collect::<Vec<_>>();
23535
23536        editor.git_restore(&Default::default(), window, cx);
23537        reverted_hunk_statuses
23538    });
23539    cx.executor().run_until_parked();
23540    cx.assert_editor_state(expected_reverted_text_with_selections);
23541    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23542}
23543
23544#[gpui::test(iterations = 10)]
23545async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23546    init_test(cx, |_| {});
23547
23548    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23549    let counter = diagnostic_requests.clone();
23550
23551    let fs = FakeFs::new(cx.executor());
23552    fs.insert_tree(
23553        path!("/a"),
23554        json!({
23555            "first.rs": "fn main() { let a = 5; }",
23556            "second.rs": "// Test file",
23557        }),
23558    )
23559    .await;
23560
23561    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23562    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23563    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23564
23565    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23566    language_registry.add(rust_lang());
23567    let mut fake_servers = language_registry.register_fake_lsp(
23568        "Rust",
23569        FakeLspAdapter {
23570            capabilities: lsp::ServerCapabilities {
23571                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23572                    lsp::DiagnosticOptions {
23573                        identifier: None,
23574                        inter_file_dependencies: true,
23575                        workspace_diagnostics: true,
23576                        work_done_progress_options: Default::default(),
23577                    },
23578                )),
23579                ..Default::default()
23580            },
23581            ..Default::default()
23582        },
23583    );
23584
23585    let editor = workspace
23586        .update(cx, |workspace, window, cx| {
23587            workspace.open_abs_path(
23588                PathBuf::from(path!("/a/first.rs")),
23589                OpenOptions::default(),
23590                window,
23591                cx,
23592            )
23593        })
23594        .unwrap()
23595        .await
23596        .unwrap()
23597        .downcast::<Editor>()
23598        .unwrap();
23599    let fake_server = fake_servers.next().await.unwrap();
23600    let server_id = fake_server.server.server_id();
23601    let mut first_request = fake_server
23602        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23603            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23604            let result_id = Some(new_result_id.to_string());
23605            assert_eq!(
23606                params.text_document.uri,
23607                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23608            );
23609            async move {
23610                Ok(lsp::DocumentDiagnosticReportResult::Report(
23611                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23612                        related_documents: None,
23613                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23614                            items: Vec::new(),
23615                            result_id,
23616                        },
23617                    }),
23618                ))
23619            }
23620        });
23621
23622    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23623        project.update(cx, |project, cx| {
23624            let buffer_id = editor
23625                .read(cx)
23626                .buffer()
23627                .read(cx)
23628                .as_singleton()
23629                .expect("created a singleton buffer")
23630                .read(cx)
23631                .remote_id();
23632            let buffer_result_id = project
23633                .lsp_store()
23634                .read(cx)
23635                .result_id(server_id, buffer_id, cx);
23636            assert_eq!(expected, buffer_result_id);
23637        });
23638    };
23639
23640    ensure_result_id(None, cx);
23641    cx.executor().advance_clock(Duration::from_millis(60));
23642    cx.executor().run_until_parked();
23643    assert_eq!(
23644        diagnostic_requests.load(atomic::Ordering::Acquire),
23645        1,
23646        "Opening file should trigger diagnostic request"
23647    );
23648    first_request
23649        .next()
23650        .await
23651        .expect("should have sent the first diagnostics pull request");
23652    ensure_result_id(Some("1".to_string()), cx);
23653
23654    // Editing should trigger diagnostics
23655    editor.update_in(cx, |editor, window, cx| {
23656        editor.handle_input("2", window, cx)
23657    });
23658    cx.executor().advance_clock(Duration::from_millis(60));
23659    cx.executor().run_until_parked();
23660    assert_eq!(
23661        diagnostic_requests.load(atomic::Ordering::Acquire),
23662        2,
23663        "Editing should trigger diagnostic request"
23664    );
23665    ensure_result_id(Some("2".to_string()), cx);
23666
23667    // Moving cursor should not trigger diagnostic request
23668    editor.update_in(cx, |editor, window, cx| {
23669        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23670            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23671        });
23672    });
23673    cx.executor().advance_clock(Duration::from_millis(60));
23674    cx.executor().run_until_parked();
23675    assert_eq!(
23676        diagnostic_requests.load(atomic::Ordering::Acquire),
23677        2,
23678        "Cursor movement should not trigger diagnostic request"
23679    );
23680    ensure_result_id(Some("2".to_string()), cx);
23681    // Multiple rapid edits should be debounced
23682    for _ in 0..5 {
23683        editor.update_in(cx, |editor, window, cx| {
23684            editor.handle_input("x", window, cx)
23685        });
23686    }
23687    cx.executor().advance_clock(Duration::from_millis(60));
23688    cx.executor().run_until_parked();
23689
23690    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23691    assert!(
23692        final_requests <= 4,
23693        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23694    );
23695    ensure_result_id(Some(final_requests.to_string()), cx);
23696}
23697
23698#[gpui::test]
23699async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23700    // Regression test for issue #11671
23701    // Previously, adding a cursor after moving multiple cursors would reset
23702    // the cursor count instead of adding to the existing cursors.
23703    init_test(cx, |_| {});
23704    let mut cx = EditorTestContext::new(cx).await;
23705
23706    // Create a simple buffer with cursor at start
23707    cx.set_state(indoc! {"
23708        ˇaaaa
23709        bbbb
23710        cccc
23711        dddd
23712        eeee
23713        ffff
23714        gggg
23715        hhhh"});
23716
23717    // Add 2 cursors below (so we have 3 total)
23718    cx.update_editor(|editor, window, cx| {
23719        editor.add_selection_below(&Default::default(), window, cx);
23720        editor.add_selection_below(&Default::default(), window, cx);
23721    });
23722
23723    // Verify we have 3 cursors
23724    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23725    assert_eq!(
23726        initial_count, 3,
23727        "Should have 3 cursors after adding 2 below"
23728    );
23729
23730    // Move down one line
23731    cx.update_editor(|editor, window, cx| {
23732        editor.move_down(&MoveDown, window, cx);
23733    });
23734
23735    // Add another cursor below
23736    cx.update_editor(|editor, window, cx| {
23737        editor.add_selection_below(&Default::default(), window, cx);
23738    });
23739
23740    // Should now have 4 cursors (3 original + 1 new)
23741    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23742    assert_eq!(
23743        final_count, 4,
23744        "Should have 4 cursors after moving and adding another"
23745    );
23746}
23747
23748#[gpui::test(iterations = 10)]
23749async fn test_document_colors(cx: &mut TestAppContext) {
23750    let expected_color = Rgba {
23751        r: 0.33,
23752        g: 0.33,
23753        b: 0.33,
23754        a: 0.33,
23755    };
23756
23757    init_test(cx, |_| {});
23758
23759    let fs = FakeFs::new(cx.executor());
23760    fs.insert_tree(
23761        path!("/a"),
23762        json!({
23763            "first.rs": "fn main() { let a = 5; }",
23764        }),
23765    )
23766    .await;
23767
23768    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23769    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23770    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23771
23772    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23773    language_registry.add(rust_lang());
23774    let mut fake_servers = language_registry.register_fake_lsp(
23775        "Rust",
23776        FakeLspAdapter {
23777            capabilities: lsp::ServerCapabilities {
23778                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23779                ..lsp::ServerCapabilities::default()
23780            },
23781            name: "rust-analyzer",
23782            ..FakeLspAdapter::default()
23783        },
23784    );
23785    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23786        "Rust",
23787        FakeLspAdapter {
23788            capabilities: lsp::ServerCapabilities {
23789                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23790                ..lsp::ServerCapabilities::default()
23791            },
23792            name: "not-rust-analyzer",
23793            ..FakeLspAdapter::default()
23794        },
23795    );
23796
23797    let editor = workspace
23798        .update(cx, |workspace, window, cx| {
23799            workspace.open_abs_path(
23800                PathBuf::from(path!("/a/first.rs")),
23801                OpenOptions::default(),
23802                window,
23803                cx,
23804            )
23805        })
23806        .unwrap()
23807        .await
23808        .unwrap()
23809        .downcast::<Editor>()
23810        .unwrap();
23811    let fake_language_server = fake_servers.next().await.unwrap();
23812    let fake_language_server_without_capabilities =
23813        fake_servers_without_capabilities.next().await.unwrap();
23814    let requests_made = Arc::new(AtomicUsize::new(0));
23815    let closure_requests_made = Arc::clone(&requests_made);
23816    let mut color_request_handle = fake_language_server
23817        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23818            let requests_made = Arc::clone(&closure_requests_made);
23819            async move {
23820                assert_eq!(
23821                    params.text_document.uri,
23822                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23823                );
23824                requests_made.fetch_add(1, atomic::Ordering::Release);
23825                Ok(vec![
23826                    lsp::ColorInformation {
23827                        range: lsp::Range {
23828                            start: lsp::Position {
23829                                line: 0,
23830                                character: 0,
23831                            },
23832                            end: lsp::Position {
23833                                line: 0,
23834                                character: 1,
23835                            },
23836                        },
23837                        color: lsp::Color {
23838                            red: 0.33,
23839                            green: 0.33,
23840                            blue: 0.33,
23841                            alpha: 0.33,
23842                        },
23843                    },
23844                    lsp::ColorInformation {
23845                        range: lsp::Range {
23846                            start: lsp::Position {
23847                                line: 0,
23848                                character: 0,
23849                            },
23850                            end: lsp::Position {
23851                                line: 0,
23852                                character: 1,
23853                            },
23854                        },
23855                        color: lsp::Color {
23856                            red: 0.33,
23857                            green: 0.33,
23858                            blue: 0.33,
23859                            alpha: 0.33,
23860                        },
23861                    },
23862                ])
23863            }
23864        });
23865
23866    let _handle = fake_language_server_without_capabilities
23867        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23868            panic!("Should not be called");
23869        });
23870    cx.executor().advance_clock(Duration::from_millis(100));
23871    color_request_handle.next().await.unwrap();
23872    cx.run_until_parked();
23873    assert_eq!(
23874        1,
23875        requests_made.load(atomic::Ordering::Acquire),
23876        "Should query for colors once per editor open"
23877    );
23878    editor.update_in(cx, |editor, _, cx| {
23879        assert_eq!(
23880            vec![expected_color],
23881            extract_color_inlays(editor, cx),
23882            "Should have an initial inlay"
23883        );
23884    });
23885
23886    // opening another file in a split should not influence the LSP query counter
23887    workspace
23888        .update(cx, |workspace, window, cx| {
23889            assert_eq!(
23890                workspace.panes().len(),
23891                1,
23892                "Should have one pane with one editor"
23893            );
23894            workspace.move_item_to_pane_in_direction(
23895                &MoveItemToPaneInDirection {
23896                    direction: SplitDirection::Right,
23897                    focus: false,
23898                    clone: true,
23899                },
23900                window,
23901                cx,
23902            );
23903        })
23904        .unwrap();
23905    cx.run_until_parked();
23906    workspace
23907        .update(cx, |workspace, _, cx| {
23908            let panes = workspace.panes();
23909            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23910            for pane in panes {
23911                let editor = pane
23912                    .read(cx)
23913                    .active_item()
23914                    .and_then(|item| item.downcast::<Editor>())
23915                    .expect("Should have opened an editor in each split");
23916                let editor_file = editor
23917                    .read(cx)
23918                    .buffer()
23919                    .read(cx)
23920                    .as_singleton()
23921                    .expect("test deals with singleton buffers")
23922                    .read(cx)
23923                    .file()
23924                    .expect("test buffese should have a file")
23925                    .path();
23926                assert_eq!(
23927                    editor_file.as_ref(),
23928                    Path::new("first.rs"),
23929                    "Both editors should be opened for the same file"
23930                )
23931            }
23932        })
23933        .unwrap();
23934
23935    cx.executor().advance_clock(Duration::from_millis(500));
23936    let save = editor.update_in(cx, |editor, window, cx| {
23937        editor.move_to_end(&MoveToEnd, window, cx);
23938        editor.handle_input("dirty", window, cx);
23939        editor.save(
23940            SaveOptions {
23941                format: true,
23942                autosave: true,
23943            },
23944            project.clone(),
23945            window,
23946            cx,
23947        )
23948    });
23949    save.await.unwrap();
23950
23951    color_request_handle.next().await.unwrap();
23952    cx.run_until_parked();
23953    assert_eq!(
23954        3,
23955        requests_made.load(atomic::Ordering::Acquire),
23956        "Should query for colors once per save and once per formatting after save"
23957    );
23958
23959    drop(editor);
23960    let close = workspace
23961        .update(cx, |workspace, window, cx| {
23962            workspace.active_pane().update(cx, |pane, cx| {
23963                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23964            })
23965        })
23966        .unwrap();
23967    close.await.unwrap();
23968    let close = workspace
23969        .update(cx, |workspace, window, cx| {
23970            workspace.active_pane().update(cx, |pane, cx| {
23971                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23972            })
23973        })
23974        .unwrap();
23975    close.await.unwrap();
23976    assert_eq!(
23977        3,
23978        requests_made.load(atomic::Ordering::Acquire),
23979        "After saving and closing all editors, no extra requests should be made"
23980    );
23981    workspace
23982        .update(cx, |workspace, _, cx| {
23983            assert!(
23984                workspace.active_item(cx).is_none(),
23985                "Should close all editors"
23986            )
23987        })
23988        .unwrap();
23989
23990    workspace
23991        .update(cx, |workspace, window, cx| {
23992            workspace.active_pane().update(cx, |pane, cx| {
23993                pane.navigate_backward(window, cx);
23994            })
23995        })
23996        .unwrap();
23997    cx.executor().advance_clock(Duration::from_millis(100));
23998    cx.run_until_parked();
23999    let editor = workspace
24000        .update(cx, |workspace, _, cx| {
24001            workspace
24002                .active_item(cx)
24003                .expect("Should have reopened the editor again after navigating back")
24004                .downcast::<Editor>()
24005                .expect("Should be an editor")
24006        })
24007        .unwrap();
24008    color_request_handle.next().await.unwrap();
24009    assert_eq!(
24010        3,
24011        requests_made.load(atomic::Ordering::Acquire),
24012        "Cache should be reused on buffer close and reopen"
24013    );
24014    editor.update(cx, |editor, cx| {
24015        assert_eq!(
24016            vec![expected_color],
24017            extract_color_inlays(editor, cx),
24018            "Should have an initial inlay"
24019        );
24020    });
24021}
24022
24023#[gpui::test]
24024async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24025    init_test(cx, |_| {});
24026    let (editor, cx) = cx.add_window_view(Editor::single_line);
24027    editor.update_in(cx, |editor, window, cx| {
24028        editor.set_text("oops\n\nwow\n", window, cx)
24029    });
24030    cx.run_until_parked();
24031    editor.update(cx, |editor, cx| {
24032        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24033    });
24034    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24035    cx.run_until_parked();
24036    editor.update(cx, |editor, cx| {
24037        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24038    });
24039}
24040
24041#[track_caller]
24042fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24043    editor
24044        .all_inlays(cx)
24045        .into_iter()
24046        .filter_map(|inlay| inlay.get_color())
24047        .map(Rgba::from)
24048        .collect()
24049}