editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_manipulate_text(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    // Test convert_to_upper_case()
 4734    cx.set_state(indoc! {"
 4735        «hello worldˇ»
 4736    "});
 4737    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4738    cx.assert_editor_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741
 4742    // Test convert_to_lower_case()
 4743    cx.set_state(indoc! {"
 4744        «HELLO WORLDˇ»
 4745    "});
 4746    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4747    cx.assert_editor_state(indoc! {"
 4748        «hello worldˇ»
 4749    "});
 4750
 4751    // Test multiple line, single selection case
 4752    cx.set_state(indoc! {"
 4753        «The quick brown
 4754        fox jumps over
 4755        the lazy dogˇ»
 4756    "});
 4757    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4758    cx.assert_editor_state(indoc! {"
 4759        «The Quick Brown
 4760        Fox Jumps Over
 4761        The Lazy Dogˇ»
 4762    "});
 4763
 4764    // Test multiple line, single selection case
 4765    cx.set_state(indoc! {"
 4766        «The quick brown
 4767        fox jumps over
 4768        the lazy dogˇ»
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «TheQuickBrown
 4775        FoxJumpsOver
 4776        TheLazyDogˇ»
 4777    "});
 4778
 4779    // From here on out, test more complex cases of manipulate_text()
 4780
 4781    // Test no selection case - should affect words cursors are in
 4782    // Cursor at beginning, middle, and end of word
 4783    cx.set_state(indoc! {"
 4784        ˇhello big beauˇtiful worldˇ
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4789    "});
 4790
 4791    // Test multiple selections on a single line and across multiple lines
 4792    cx.set_state(indoc! {"
 4793        «Theˇ» quick «brown
 4794        foxˇ» jumps «overˇ»
 4795        the «lazyˇ» dog
 4796    "});
 4797    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4798    cx.assert_editor_state(indoc! {"
 4799        «THEˇ» quick «BROWN
 4800        FOXˇ» jumps «OVERˇ»
 4801        the «LAZYˇ» dog
 4802    "});
 4803
 4804    // Test case where text length grows
 4805    cx.set_state(indoc! {"
 4806        «tschüߡ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «TSCHÜSSˇ»
 4811    "});
 4812
 4813    // Test to make sure we don't crash when text shrinks
 4814    cx.set_state(indoc! {"
 4815        aaa_bbbˇ
 4816    "});
 4817    cx.update_editor(|e, window, cx| {
 4818        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4819    });
 4820    cx.assert_editor_state(indoc! {"
 4821        «aaaBbbˇ»
 4822    "});
 4823
 4824    // Test to make sure we all aware of the fact that each word can grow and shrink
 4825    // Final selections should be aware of this fact
 4826    cx.set_state(indoc! {"
 4827        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4834    "});
 4835
 4836    cx.set_state(indoc! {"
 4837        «hElLo, WoRld!ˇ»
 4838    "});
 4839    cx.update_editor(|e, window, cx| {
 4840        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4841    });
 4842    cx.assert_editor_state(indoc! {"
 4843        «HeLlO, wOrLD!ˇ»
 4844    "});
 4845}
 4846
 4847#[gpui::test]
 4848fn test_duplicate_line(cx: &mut TestAppContext) {
 4849    init_test(cx, |_| {});
 4850
 4851    let editor = cx.add_window(|window, cx| {
 4852        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4853        build_editor(buffer, window, cx)
 4854    });
 4855    _ = editor.update(cx, |editor, window, cx| {
 4856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4857            s.select_display_ranges([
 4858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4859                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4860                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4861                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4862            ])
 4863        });
 4864        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4865        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4866        assert_eq!(
 4867            editor.selections.display_ranges(cx),
 4868            vec![
 4869                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4870                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4871                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4872                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4873            ]
 4874        );
 4875    });
 4876
 4877    let editor = cx.add_window(|window, cx| {
 4878        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4879        build_editor(buffer, window, cx)
 4880    });
 4881    _ = editor.update(cx, |editor, window, cx| {
 4882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4883            s.select_display_ranges([
 4884                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4885                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4886            ])
 4887        });
 4888        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4889        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4890        assert_eq!(
 4891            editor.selections.display_ranges(cx),
 4892            vec![
 4893                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4894                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4895            ]
 4896        );
 4897    });
 4898
 4899    // With `move_upwards` the selections stay in place, except for
 4900    // the lines inserted above them
 4901    let editor = cx.add_window(|window, cx| {
 4902        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4903        build_editor(buffer, window, cx)
 4904    });
 4905    _ = editor.update(cx, |editor, window, cx| {
 4906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4907            s.select_display_ranges([
 4908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4909                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4910                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4911                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4912            ])
 4913        });
 4914        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4915        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4916        assert_eq!(
 4917            editor.selections.display_ranges(cx),
 4918            vec![
 4919                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4920                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4921                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4922                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4923            ]
 4924        );
 4925    });
 4926
 4927    let editor = cx.add_window(|window, cx| {
 4928        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4929        build_editor(buffer, window, cx)
 4930    });
 4931    _ = editor.update(cx, |editor, window, cx| {
 4932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4933            s.select_display_ranges([
 4934                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4935                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4936            ])
 4937        });
 4938        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4939        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4940        assert_eq!(
 4941            editor.selections.display_ranges(cx),
 4942            vec![
 4943                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4944                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4945            ]
 4946        );
 4947    });
 4948
 4949    let editor = cx.add_window(|window, cx| {
 4950        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4951        build_editor(buffer, window, cx)
 4952    });
 4953    _ = editor.update(cx, |editor, window, cx| {
 4954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4955            s.select_display_ranges([
 4956                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4957                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4958            ])
 4959        });
 4960        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4961        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4962        assert_eq!(
 4963            editor.selections.display_ranges(cx),
 4964            vec![
 4965                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4966                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4967            ]
 4968        );
 4969    });
 4970}
 4971
 4972#[gpui::test]
 4973fn test_move_line_up_down(cx: &mut TestAppContext) {
 4974    init_test(cx, |_| {});
 4975
 4976    let editor = cx.add_window(|window, cx| {
 4977        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4978        build_editor(buffer, window, cx)
 4979    });
 4980    _ = editor.update(cx, |editor, window, cx| {
 4981        editor.fold_creases(
 4982            vec![
 4983                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4984                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4985                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4986            ],
 4987            true,
 4988            window,
 4989            cx,
 4990        );
 4991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4992            s.select_display_ranges([
 4993                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4994                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4995                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4997            ])
 4998        });
 4999        assert_eq!(
 5000            editor.display_text(cx),
 5001            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5002        );
 5003
 5004        editor.move_line_up(&MoveLineUp, window, cx);
 5005        assert_eq!(
 5006            editor.display_text(cx),
 5007            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5008        );
 5009        assert_eq!(
 5010            editor.selections.display_ranges(cx),
 5011            vec![
 5012                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5013                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5014                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5015                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5016            ]
 5017        );
 5018    });
 5019
 5020    _ = editor.update(cx, |editor, window, cx| {
 5021        editor.move_line_down(&MoveLineDown, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5030                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5031                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5032                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_up(&MoveLineUp, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5064                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5065                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5066                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5067            ]
 5068        );
 5069    });
 5070}
 5071
 5072#[gpui::test]
 5073fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5074    init_test(cx, |_| {});
 5075    let editor = cx.add_window(|window, cx| {
 5076        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5077        build_editor(buffer, window, cx)
 5078    });
 5079    _ = editor.update(cx, |editor, window, cx| {
 5080        editor.fold_creases(
 5081            vec![Crease::simple(
 5082                Point::new(6, 4)..Point::new(7, 4),
 5083                FoldPlaceholder::test(),
 5084            )],
 5085            true,
 5086            window,
 5087            cx,
 5088        );
 5089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5090            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5091        });
 5092        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5093        editor.move_line_up(&MoveLineUp, window, cx);
 5094        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5095        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5096    });
 5097}
 5098
 5099#[gpui::test]
 5100fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5101    init_test(cx, |_| {});
 5102
 5103    let editor = cx.add_window(|window, cx| {
 5104        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5105        build_editor(buffer, window, cx)
 5106    });
 5107    _ = editor.update(cx, |editor, window, cx| {
 5108        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5109        editor.insert_blocks(
 5110            [BlockProperties {
 5111                style: BlockStyle::Fixed,
 5112                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5113                height: Some(1),
 5114                render: Arc::new(|_| div().into_any()),
 5115                priority: 0,
 5116            }],
 5117            Some(Autoscroll::fit()),
 5118            cx,
 5119        );
 5120        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5121            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5122        });
 5123        editor.move_line_down(&MoveLineDown, window, cx);
 5124    });
 5125}
 5126
 5127#[gpui::test]
 5128async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5129    init_test(cx, |_| {});
 5130
 5131    let mut cx = EditorTestContext::new(cx).await;
 5132    cx.set_state(
 5133        &"
 5134            ˇzero
 5135            one
 5136            two
 5137            three
 5138            four
 5139            five
 5140        "
 5141        .unindent(),
 5142    );
 5143
 5144    // Create a four-line block that replaces three lines of text.
 5145    cx.update_editor(|editor, window, cx| {
 5146        let snapshot = editor.snapshot(window, cx);
 5147        let snapshot = &snapshot.buffer_snapshot;
 5148        let placement = BlockPlacement::Replace(
 5149            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5150        );
 5151        editor.insert_blocks(
 5152            [BlockProperties {
 5153                placement,
 5154                height: Some(4),
 5155                style: BlockStyle::Sticky,
 5156                render: Arc::new(|_| gpui::div().into_any_element()),
 5157                priority: 0,
 5158            }],
 5159            None,
 5160            cx,
 5161        );
 5162    });
 5163
 5164    // Move down so that the cursor touches the block.
 5165    cx.update_editor(|editor, window, cx| {
 5166        editor.move_down(&Default::default(), window, cx);
 5167    });
 5168    cx.assert_editor_state(
 5169        &"
 5170            zero
 5171            «one
 5172            two
 5173            threeˇ»
 5174            four
 5175            five
 5176        "
 5177        .unindent(),
 5178    );
 5179
 5180    // Move down past the block.
 5181    cx.update_editor(|editor, window, cx| {
 5182        editor.move_down(&Default::default(), window, cx);
 5183    });
 5184    cx.assert_editor_state(
 5185        &"
 5186            zero
 5187            one
 5188            two
 5189            three
 5190            ˇfour
 5191            five
 5192        "
 5193        .unindent(),
 5194    );
 5195}
 5196
 5197#[gpui::test]
 5198fn test_transpose(cx: &mut TestAppContext) {
 5199    init_test(cx, |_| {});
 5200
 5201    _ = cx.add_window(|window, cx| {
 5202        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5203        editor.set_style(EditorStyle::default(), window, cx);
 5204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5205            s.select_ranges([1..1])
 5206        });
 5207        editor.transpose(&Default::default(), window, cx);
 5208        assert_eq!(editor.text(cx), "bac");
 5209        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5210
 5211        editor.transpose(&Default::default(), window, cx);
 5212        assert_eq!(editor.text(cx), "bca");
 5213        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5214
 5215        editor.transpose(&Default::default(), window, cx);
 5216        assert_eq!(editor.text(cx), "bac");
 5217        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5218
 5219        editor
 5220    });
 5221
 5222    _ = cx.add_window(|window, cx| {
 5223        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5224        editor.set_style(EditorStyle::default(), window, cx);
 5225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5226            s.select_ranges([3..3])
 5227        });
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "acb\nde");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5231
 5232        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5233            s.select_ranges([4..4])
 5234        });
 5235        editor.transpose(&Default::default(), window, cx);
 5236        assert_eq!(editor.text(cx), "acbd\ne");
 5237        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5238
 5239        editor.transpose(&Default::default(), window, cx);
 5240        assert_eq!(editor.text(cx), "acbde\n");
 5241        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5242
 5243        editor.transpose(&Default::default(), window, cx);
 5244        assert_eq!(editor.text(cx), "acbd\ne");
 5245        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5246
 5247        editor
 5248    });
 5249
 5250    _ = cx.add_window(|window, cx| {
 5251        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5252        editor.set_style(EditorStyle::default(), window, cx);
 5253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5254            s.select_ranges([1..1, 2..2, 4..4])
 5255        });
 5256        editor.transpose(&Default::default(), window, cx);
 5257        assert_eq!(editor.text(cx), "bacd\ne");
 5258        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5259
 5260        editor.transpose(&Default::default(), window, cx);
 5261        assert_eq!(editor.text(cx), "bcade\n");
 5262        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5263
 5264        editor.transpose(&Default::default(), window, cx);
 5265        assert_eq!(editor.text(cx), "bcda\ne");
 5266        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5267
 5268        editor.transpose(&Default::default(), window, cx);
 5269        assert_eq!(editor.text(cx), "bcade\n");
 5270        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5271
 5272        editor.transpose(&Default::default(), window, cx);
 5273        assert_eq!(editor.text(cx), "bcaed\n");
 5274        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5275
 5276        editor
 5277    });
 5278
 5279    _ = cx.add_window(|window, cx| {
 5280        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5281        editor.set_style(EditorStyle::default(), window, cx);
 5282        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5283            s.select_ranges([4..4])
 5284        });
 5285        editor.transpose(&Default::default(), window, cx);
 5286        assert_eq!(editor.text(cx), "🏀🍐✋");
 5287        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5288
 5289        editor.transpose(&Default::default(), window, cx);
 5290        assert_eq!(editor.text(cx), "🏀✋🍐");
 5291        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5292
 5293        editor.transpose(&Default::default(), window, cx);
 5294        assert_eq!(editor.text(cx), "🏀🍐✋");
 5295        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5296
 5297        editor
 5298    });
 5299}
 5300
 5301#[gpui::test]
 5302async fn test_rewrap(cx: &mut TestAppContext) {
 5303    init_test(cx, |settings| {
 5304        settings.languages.0.extend([
 5305            (
 5306                "Markdown".into(),
 5307                LanguageSettingsContent {
 5308                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5309                    preferred_line_length: Some(40),
 5310                    ..Default::default()
 5311                },
 5312            ),
 5313            (
 5314                "Plain Text".into(),
 5315                LanguageSettingsContent {
 5316                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5317                    preferred_line_length: Some(40),
 5318                    ..Default::default()
 5319                },
 5320            ),
 5321            (
 5322                "C++".into(),
 5323                LanguageSettingsContent {
 5324                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5325                    preferred_line_length: Some(40),
 5326                    ..Default::default()
 5327                },
 5328            ),
 5329            (
 5330                "Python".into(),
 5331                LanguageSettingsContent {
 5332                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5333                    preferred_line_length: Some(40),
 5334                    ..Default::default()
 5335                },
 5336            ),
 5337            (
 5338                "Rust".into(),
 5339                LanguageSettingsContent {
 5340                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5341                    preferred_line_length: Some(40),
 5342                    ..Default::default()
 5343                },
 5344            ),
 5345        ])
 5346    });
 5347
 5348    let mut cx = EditorTestContext::new(cx).await;
 5349
 5350    let cpp_language = Arc::new(Language::new(
 5351        LanguageConfig {
 5352            name: "C++".into(),
 5353            line_comments: vec!["// ".into()],
 5354            ..LanguageConfig::default()
 5355        },
 5356        None,
 5357    ));
 5358    let python_language = Arc::new(Language::new(
 5359        LanguageConfig {
 5360            name: "Python".into(),
 5361            line_comments: vec!["# ".into()],
 5362            ..LanguageConfig::default()
 5363        },
 5364        None,
 5365    ));
 5366    let markdown_language = Arc::new(Language::new(
 5367        LanguageConfig {
 5368            name: "Markdown".into(),
 5369            rewrap_prefixes: vec![
 5370                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5371                regex::Regex::new("[-*+]\\s+").unwrap(),
 5372            ],
 5373            ..LanguageConfig::default()
 5374        },
 5375        None,
 5376    ));
 5377    let rust_language = Arc::new(Language::new(
 5378        LanguageConfig {
 5379            name: "Rust".into(),
 5380            line_comments: vec!["// ".into(), "/// ".into()],
 5381            ..LanguageConfig::default()
 5382        },
 5383        Some(tree_sitter_rust::LANGUAGE.into()),
 5384    ));
 5385
 5386    let plaintext_language = Arc::new(Language::new(
 5387        LanguageConfig {
 5388            name: "Plain Text".into(),
 5389            ..LanguageConfig::default()
 5390        },
 5391        None,
 5392    ));
 5393
 5394    // Test basic rewrapping of a long line with a cursor
 5395    assert_rewrap(
 5396        indoc! {"
 5397            // ˇThis is a long comment that needs to be wrapped.
 5398        "},
 5399        indoc! {"
 5400            // ˇThis is a long comment that needs to
 5401            // be wrapped.
 5402        "},
 5403        cpp_language.clone(),
 5404        &mut cx,
 5405    );
 5406
 5407    // Test rewrapping a full selection
 5408    assert_rewrap(
 5409        indoc! {"
 5410            «// This selected long comment needs to be wrapped.ˇ»"
 5411        },
 5412        indoc! {"
 5413            «// This selected long comment needs to
 5414            // be wrapped.ˇ»"
 5415        },
 5416        cpp_language.clone(),
 5417        &mut cx,
 5418    );
 5419
 5420    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5421    assert_rewrap(
 5422        indoc! {"
 5423            // ˇThis is the first line.
 5424            // Thisˇ is the second line.
 5425            // This is the thirdˇ line, all part of one paragraph.
 5426         "},
 5427        indoc! {"
 5428            // ˇThis is the first line. Thisˇ is the
 5429            // second line. This is the thirdˇ line,
 5430            // all part of one paragraph.
 5431         "},
 5432        cpp_language.clone(),
 5433        &mut cx,
 5434    );
 5435
 5436    // Test multiple cursors in different paragraphs trigger separate rewraps
 5437    assert_rewrap(
 5438        indoc! {"
 5439            // ˇThis is the first paragraph, first line.
 5440            // ˇThis is the first paragraph, second line.
 5441
 5442            // ˇThis is the second paragraph, first line.
 5443            // ˇThis is the second paragraph, second line.
 5444        "},
 5445        indoc! {"
 5446            // ˇThis is the first paragraph, first
 5447            // line. ˇThis is the first paragraph,
 5448            // second line.
 5449
 5450            // ˇThis is the second paragraph, first
 5451            // line. ˇThis is the second paragraph,
 5452            // second line.
 5453        "},
 5454        cpp_language.clone(),
 5455        &mut cx,
 5456    );
 5457
 5458    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5459    assert_rewrap(
 5460        indoc! {"
 5461            «// A regular long long comment to be wrapped.
 5462            /// A documentation long comment to be wrapped.ˇ»
 5463          "},
 5464        indoc! {"
 5465            «// A regular long long comment to be
 5466            // wrapped.
 5467            /// A documentation long comment to be
 5468            /// wrapped.ˇ»
 5469          "},
 5470        rust_language.clone(),
 5471        &mut cx,
 5472    );
 5473
 5474    // Test that change in indentation level trigger seperate rewraps
 5475    assert_rewrap(
 5476        indoc! {"
 5477            fn foo() {
 5478                «// This is a long comment at the base indent.
 5479                    // This is a long comment at the next indent.ˇ»
 5480            }
 5481        "},
 5482        indoc! {"
 5483            fn foo() {
 5484                «// This is a long comment at the
 5485                // base indent.
 5486                    // This is a long comment at the
 5487                    // next indent.ˇ»
 5488            }
 5489        "},
 5490        rust_language.clone(),
 5491        &mut cx,
 5492    );
 5493
 5494    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5495    assert_rewrap(
 5496        indoc! {"
 5497            # ˇThis is a long comment using a pound sign.
 5498        "},
 5499        indoc! {"
 5500            # ˇThis is a long comment using a pound
 5501            # sign.
 5502        "},
 5503        python_language.clone(),
 5504        &mut cx,
 5505    );
 5506
 5507    // Test rewrapping only affects comments, not code even when selected
 5508    assert_rewrap(
 5509        indoc! {"
 5510            «/// This doc comment is long and should be wrapped.
 5511            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5512        "},
 5513        indoc! {"
 5514            «/// This doc comment is long and should
 5515            /// be wrapped.
 5516            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5517        "},
 5518        rust_language.clone(),
 5519        &mut cx,
 5520    );
 5521
 5522    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5523    assert_rewrap(
 5524        indoc! {"
 5525            # Header
 5526
 5527            A long long long line of markdown text to wrap.ˇ
 5528         "},
 5529        indoc! {"
 5530            # Header
 5531
 5532            A long long long line of markdown text
 5533            to wrap.ˇ
 5534         "},
 5535        markdown_language.clone(),
 5536        &mut cx,
 5537    );
 5538
 5539    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5540    assert_rewrap(
 5541        indoc! {"
 5542            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5543            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5544            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5545        "},
 5546        indoc! {"
 5547            «1. This is a numbered list item that is
 5548               very long and needs to be wrapped
 5549               properly.
 5550            2. This is a numbered list item that is
 5551               very long and needs to be wrapped
 5552               properly.
 5553            - This is an unordered list item that is
 5554              also very long and should not merge
 5555              with the numbered item.ˇ»
 5556        "},
 5557        markdown_language.clone(),
 5558        &mut cx,
 5559    );
 5560
 5561    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5562    assert_rewrap(
 5563        indoc! {"
 5564            «1. This is a numbered list item that is
 5565            very long and needs to be wrapped
 5566            properly.
 5567            2. This is a numbered list item that is
 5568            very long and needs to be wrapped
 5569            properly.
 5570            - This is an unordered list item that is
 5571            also very long and should not merge with
 5572            the numbered item.ˇ»
 5573        "},
 5574        indoc! {"
 5575            «1. This is a numbered list item that is
 5576               very long and needs to be wrapped
 5577               properly.
 5578            2. This is a numbered list item that is
 5579               very long and needs to be wrapped
 5580               properly.
 5581            - This is an unordered list item that is
 5582              also very long and should not merge
 5583              with the numbered item.ˇ»
 5584        "},
 5585        markdown_language.clone(),
 5586        &mut cx,
 5587    );
 5588
 5589    // Test that rewrapping maintain indents even when they already exists.
 5590    assert_rewrap(
 5591        indoc! {"
 5592            «1. This is a numbered list
 5593               item that is very long and needs to be wrapped properly.
 5594            2. This is a numbered list
 5595               item that is very long and needs to be wrapped properly.
 5596            - This is an unordered list item that is also very long and
 5597              should not merge with the numbered item.ˇ»
 5598        "},
 5599        indoc! {"
 5600            «1. This is a numbered list item that is
 5601               very long and needs to be wrapped
 5602               properly.
 5603            2. This is a numbered list item that is
 5604               very long and needs to be wrapped
 5605               properly.
 5606            - This is an unordered list item that is
 5607              also very long and should not merge
 5608              with the numbered item.ˇ»
 5609        "},
 5610        markdown_language.clone(),
 5611        &mut cx,
 5612    );
 5613
 5614    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5615    assert_rewrap(
 5616        indoc! {"
 5617            ˇThis is a very long line of plain text that will be wrapped.
 5618        "},
 5619        indoc! {"
 5620            ˇThis is a very long line of plain text
 5621            that will be wrapped.
 5622        "},
 5623        plaintext_language.clone(),
 5624        &mut cx,
 5625    );
 5626
 5627    // Test that non-commented code acts as a paragraph boundary within a selection
 5628    assert_rewrap(
 5629        indoc! {"
 5630               «// This is the first long comment block to be wrapped.
 5631               fn my_func(a: u32);
 5632               // This is the second long comment block to be wrapped.ˇ»
 5633           "},
 5634        indoc! {"
 5635               «// This is the first long comment block
 5636               // to be wrapped.
 5637               fn my_func(a: u32);
 5638               // This is the second long comment block
 5639               // to be wrapped.ˇ»
 5640           "},
 5641        rust_language.clone(),
 5642        &mut cx,
 5643    );
 5644
 5645    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5646    assert_rewrap(
 5647        indoc! {"
 5648            «ˇThis is a very long line that will be wrapped.
 5649
 5650            This is another paragraph in the same selection.»
 5651
 5652            «\tThis is a very long indented line that will be wrapped.ˇ»
 5653         "},
 5654        indoc! {"
 5655            «ˇThis is a very long line that will be
 5656            wrapped.
 5657
 5658            This is another paragraph in the same
 5659            selection.»
 5660
 5661            «\tThis is a very long indented line
 5662            \tthat will be wrapped.ˇ»
 5663         "},
 5664        plaintext_language.clone(),
 5665        &mut cx,
 5666    );
 5667
 5668    // Test that an empty comment line acts as a paragraph boundary
 5669    assert_rewrap(
 5670        indoc! {"
 5671            // ˇThis is a long comment that will be wrapped.
 5672            //
 5673            // And this is another long comment that will also be wrapped.ˇ
 5674         "},
 5675        indoc! {"
 5676            // ˇThis is a long comment that will be
 5677            // wrapped.
 5678            //
 5679            // And this is another long comment that
 5680            // will also be wrapped.ˇ
 5681         "},
 5682        cpp_language,
 5683        &mut cx,
 5684    );
 5685
 5686    #[track_caller]
 5687    fn assert_rewrap(
 5688        unwrapped_text: &str,
 5689        wrapped_text: &str,
 5690        language: Arc<Language>,
 5691        cx: &mut EditorTestContext,
 5692    ) {
 5693        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5694        cx.set_state(unwrapped_text);
 5695        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5696        cx.assert_editor_state(wrapped_text);
 5697    }
 5698}
 5699
 5700#[gpui::test]
 5701async fn test_hard_wrap(cx: &mut TestAppContext) {
 5702    init_test(cx, |_| {});
 5703    let mut cx = EditorTestContext::new(cx).await;
 5704
 5705    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5706    cx.update_editor(|editor, _, cx| {
 5707        editor.set_hard_wrap(Some(14), cx);
 5708    });
 5709
 5710    cx.set_state(indoc!(
 5711        "
 5712        one two three ˇ
 5713        "
 5714    ));
 5715    cx.simulate_input("four");
 5716    cx.run_until_parked();
 5717
 5718    cx.assert_editor_state(indoc!(
 5719        "
 5720        one two three
 5721        fourˇ
 5722        "
 5723    ));
 5724
 5725    cx.update_editor(|editor, window, cx| {
 5726        editor.newline(&Default::default(), window, cx);
 5727    });
 5728    cx.run_until_parked();
 5729    cx.assert_editor_state(indoc!(
 5730        "
 5731        one two three
 5732        four
 5733        ˇ
 5734        "
 5735    ));
 5736
 5737    cx.simulate_input("five");
 5738    cx.run_until_parked();
 5739    cx.assert_editor_state(indoc!(
 5740        "
 5741        one two three
 5742        four
 5743        fiveˇ
 5744        "
 5745    ));
 5746
 5747    cx.update_editor(|editor, window, cx| {
 5748        editor.newline(&Default::default(), window, cx);
 5749    });
 5750    cx.run_until_parked();
 5751    cx.simulate_input("# ");
 5752    cx.run_until_parked();
 5753    cx.assert_editor_state(indoc!(
 5754        "
 5755        one two three
 5756        four
 5757        five
 5758        # ˇ
 5759        "
 5760    ));
 5761
 5762    cx.update_editor(|editor, window, cx| {
 5763        editor.newline(&Default::default(), window, cx);
 5764    });
 5765    cx.run_until_parked();
 5766    cx.assert_editor_state(indoc!(
 5767        "
 5768        one two three
 5769        four
 5770        five
 5771        #\x20
 5772 5773        "
 5774    ));
 5775
 5776    cx.simulate_input(" 6");
 5777    cx.run_until_parked();
 5778    cx.assert_editor_state(indoc!(
 5779        "
 5780        one two three
 5781        four
 5782        five
 5783        #
 5784        # 6ˇ
 5785        "
 5786    ));
 5787}
 5788
 5789#[gpui::test]
 5790async fn test_clipboard(cx: &mut TestAppContext) {
 5791    init_test(cx, |_| {});
 5792
 5793    let mut cx = EditorTestContext::new(cx).await;
 5794
 5795    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5796    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5797    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5798
 5799    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5800    cx.set_state("two ˇfour ˇsix ˇ");
 5801    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5802    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5803
 5804    // Paste again but with only two cursors. Since the number of cursors doesn't
 5805    // match the number of slices in the clipboard, the entire clipboard text
 5806    // is pasted at each cursor.
 5807    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5808    cx.update_editor(|e, window, cx| {
 5809        e.handle_input("( ", window, cx);
 5810        e.paste(&Paste, window, cx);
 5811        e.handle_input(") ", window, cx);
 5812    });
 5813    cx.assert_editor_state(
 5814        &([
 5815            "( one✅ ",
 5816            "three ",
 5817            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5818            "three ",
 5819            "five ) ˇ",
 5820        ]
 5821        .join("\n")),
 5822    );
 5823
 5824    // Cut with three selections, one of which is full-line.
 5825    cx.set_state(indoc! {"
 5826        1«2ˇ»3
 5827        4ˇ567
 5828        «8ˇ»9"});
 5829    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5830    cx.assert_editor_state(indoc! {"
 5831        1ˇ3
 5832        ˇ9"});
 5833
 5834    // Paste with three selections, noticing how the copied selection that was full-line
 5835    // gets inserted before the second cursor.
 5836    cx.set_state(indoc! {"
 5837        1ˇ3
 5838 5839        «oˇ»ne"});
 5840    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5841    cx.assert_editor_state(indoc! {"
 5842        12ˇ3
 5843        4567
 5844 5845        8ˇne"});
 5846
 5847    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5848    cx.set_state(indoc! {"
 5849        The quick brown
 5850        fox juˇmps over
 5851        the lazy dog"});
 5852    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5853    assert_eq!(
 5854        cx.read_from_clipboard()
 5855            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5856        Some("fox jumps over\n".to_string())
 5857    );
 5858
 5859    // Paste with three selections, noticing how the copied full-line selection is inserted
 5860    // before the empty selections but replaces the selection that is non-empty.
 5861    cx.set_state(indoc! {"
 5862        Tˇhe quick brown
 5863        «foˇ»x jumps over
 5864        tˇhe lazy dog"});
 5865    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5866    cx.assert_editor_state(indoc! {"
 5867        fox jumps over
 5868        Tˇhe quick brown
 5869        fox jumps over
 5870        ˇx jumps over
 5871        fox jumps over
 5872        tˇhe lazy dog"});
 5873}
 5874
 5875#[gpui::test]
 5876async fn test_copy_trim(cx: &mut TestAppContext) {
 5877    init_test(cx, |_| {});
 5878
 5879    let mut cx = EditorTestContext::new(cx).await;
 5880    cx.set_state(
 5881        r#"            «for selection in selections.iter() {
 5882            let mut start = selection.start;
 5883            let mut end = selection.end;
 5884            let is_entire_line = selection.is_empty();
 5885            if is_entire_line {
 5886                start = Point::new(start.row, 0);ˇ»
 5887                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5888            }
 5889        "#,
 5890    );
 5891    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5892    assert_eq!(
 5893        cx.read_from_clipboard()
 5894            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5895        Some(
 5896            "for selection in selections.iter() {
 5897            let mut start = selection.start;
 5898            let mut end = selection.end;
 5899            let is_entire_line = selection.is_empty();
 5900            if is_entire_line {
 5901                start = Point::new(start.row, 0);"
 5902                .to_string()
 5903        ),
 5904        "Regular copying preserves all indentation selected",
 5905    );
 5906    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5907    assert_eq!(
 5908        cx.read_from_clipboard()
 5909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5910        Some(
 5911            "for selection in selections.iter() {
 5912let mut start = selection.start;
 5913let mut end = selection.end;
 5914let is_entire_line = selection.is_empty();
 5915if is_entire_line {
 5916    start = Point::new(start.row, 0);"
 5917                .to_string()
 5918        ),
 5919        "Copying with stripping should strip all leading whitespaces"
 5920    );
 5921
 5922    cx.set_state(
 5923        r#"       «     for selection in selections.iter() {
 5924            let mut start = selection.start;
 5925            let mut end = selection.end;
 5926            let is_entire_line = selection.is_empty();
 5927            if is_entire_line {
 5928                start = Point::new(start.row, 0);ˇ»
 5929                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5930            }
 5931        "#,
 5932    );
 5933    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5934    assert_eq!(
 5935        cx.read_from_clipboard()
 5936            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5937        Some(
 5938            "     for selection in selections.iter() {
 5939            let mut start = selection.start;
 5940            let mut end = selection.end;
 5941            let is_entire_line = selection.is_empty();
 5942            if is_entire_line {
 5943                start = Point::new(start.row, 0);"
 5944                .to_string()
 5945        ),
 5946        "Regular copying preserves all indentation selected",
 5947    );
 5948    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5949    assert_eq!(
 5950        cx.read_from_clipboard()
 5951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5952        Some(
 5953            "for selection in selections.iter() {
 5954let mut start = selection.start;
 5955let mut end = selection.end;
 5956let is_entire_line = selection.is_empty();
 5957if is_entire_line {
 5958    start = Point::new(start.row, 0);"
 5959                .to_string()
 5960        ),
 5961        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5962    );
 5963
 5964    cx.set_state(
 5965        r#"       «ˇ     for selection in selections.iter() {
 5966            let mut start = selection.start;
 5967            let mut end = selection.end;
 5968            let is_entire_line = selection.is_empty();
 5969            if is_entire_line {
 5970                start = Point::new(start.row, 0);»
 5971                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5972            }
 5973        "#,
 5974    );
 5975    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5976    assert_eq!(
 5977        cx.read_from_clipboard()
 5978            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5979        Some(
 5980            "     for selection in selections.iter() {
 5981            let mut start = selection.start;
 5982            let mut end = selection.end;
 5983            let is_entire_line = selection.is_empty();
 5984            if is_entire_line {
 5985                start = Point::new(start.row, 0);"
 5986                .to_string()
 5987        ),
 5988        "Regular copying for reverse selection works the same",
 5989    );
 5990    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5991    assert_eq!(
 5992        cx.read_from_clipboard()
 5993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5994        Some(
 5995            "for selection in selections.iter() {
 5996let mut start = selection.start;
 5997let mut end = selection.end;
 5998let is_entire_line = selection.is_empty();
 5999if is_entire_line {
 6000    start = Point::new(start.row, 0);"
 6001                .to_string()
 6002        ),
 6003        "Copying with stripping for reverse selection works the same"
 6004    );
 6005
 6006    cx.set_state(
 6007        r#"            for selection «in selections.iter() {
 6008            let mut start = selection.start;
 6009            let mut end = selection.end;
 6010            let is_entire_line = selection.is_empty();
 6011            if is_entire_line {
 6012                start = Point::new(start.row, 0);ˇ»
 6013                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6014            }
 6015        "#,
 6016    );
 6017    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6018    assert_eq!(
 6019        cx.read_from_clipboard()
 6020            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6021        Some(
 6022            "in selections.iter() {
 6023            let mut start = selection.start;
 6024            let mut end = selection.end;
 6025            let is_entire_line = selection.is_empty();
 6026            if is_entire_line {
 6027                start = Point::new(start.row, 0);"
 6028                .to_string()
 6029        ),
 6030        "When selecting past the indent, the copying works as usual",
 6031    );
 6032    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6033    assert_eq!(
 6034        cx.read_from_clipboard()
 6035            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6036        Some(
 6037            "in selections.iter() {
 6038            let mut start = selection.start;
 6039            let mut end = selection.end;
 6040            let is_entire_line = selection.is_empty();
 6041            if is_entire_line {
 6042                start = Point::new(start.row, 0);"
 6043                .to_string()
 6044        ),
 6045        "When selecting past the indent, nothing is trimmed"
 6046    );
 6047
 6048    cx.set_state(
 6049        r#"            «for selection in selections.iter() {
 6050            let mut start = selection.start;
 6051
 6052            let mut end = selection.end;
 6053            let is_entire_line = selection.is_empty();
 6054            if is_entire_line {
 6055                start = Point::new(start.row, 0);
 6056ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6057            }
 6058        "#,
 6059    );
 6060    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6061    assert_eq!(
 6062        cx.read_from_clipboard()
 6063            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6064        Some(
 6065            "for selection in selections.iter() {
 6066let mut start = selection.start;
 6067
 6068let mut end = selection.end;
 6069let is_entire_line = selection.is_empty();
 6070if is_entire_line {
 6071    start = Point::new(start.row, 0);
 6072"
 6073            .to_string()
 6074        ),
 6075        "Copying with stripping should ignore empty lines"
 6076    );
 6077}
 6078
 6079#[gpui::test]
 6080async fn test_paste_multiline(cx: &mut TestAppContext) {
 6081    init_test(cx, |_| {});
 6082
 6083    let mut cx = EditorTestContext::new(cx).await;
 6084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6085
 6086    // Cut an indented block, without the leading whitespace.
 6087    cx.set_state(indoc! {"
 6088        const a: B = (
 6089            c(),
 6090            «d(
 6091                e,
 6092                f
 6093            )ˇ»
 6094        );
 6095    "});
 6096    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6097    cx.assert_editor_state(indoc! {"
 6098        const a: B = (
 6099            c(),
 6100            ˇ
 6101        );
 6102    "});
 6103
 6104    // Paste it at the same position.
 6105    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6106    cx.assert_editor_state(indoc! {"
 6107        const a: B = (
 6108            c(),
 6109            d(
 6110                e,
 6111                f
 6112 6113        );
 6114    "});
 6115
 6116    // Paste it at a line with a lower indent level.
 6117    cx.set_state(indoc! {"
 6118        ˇ
 6119        const a: B = (
 6120            c(),
 6121        );
 6122    "});
 6123    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6124    cx.assert_editor_state(indoc! {"
 6125        d(
 6126            e,
 6127            f
 6128 6129        const a: B = (
 6130            c(),
 6131        );
 6132    "});
 6133
 6134    // Cut an indented block, with the leading whitespace.
 6135    cx.set_state(indoc! {"
 6136        const a: B = (
 6137            c(),
 6138        «    d(
 6139                e,
 6140                f
 6141            )
 6142        ˇ»);
 6143    "});
 6144    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6145    cx.assert_editor_state(indoc! {"
 6146        const a: B = (
 6147            c(),
 6148        ˇ);
 6149    "});
 6150
 6151    // Paste it at the same position.
 6152    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6153    cx.assert_editor_state(indoc! {"
 6154        const a: B = (
 6155            c(),
 6156            d(
 6157                e,
 6158                f
 6159            )
 6160        ˇ);
 6161    "});
 6162
 6163    // Paste it at a line with a higher indent level.
 6164    cx.set_state(indoc! {"
 6165        const a: B = (
 6166            c(),
 6167            d(
 6168                e,
 6169 6170            )
 6171        );
 6172    "});
 6173    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6174    cx.assert_editor_state(indoc! {"
 6175        const a: B = (
 6176            c(),
 6177            d(
 6178                e,
 6179                f    d(
 6180                    e,
 6181                    f
 6182                )
 6183        ˇ
 6184            )
 6185        );
 6186    "});
 6187
 6188    // Copy an indented block, starting mid-line
 6189    cx.set_state(indoc! {"
 6190        const a: B = (
 6191            c(),
 6192            somethin«g(
 6193                e,
 6194                f
 6195            )ˇ»
 6196        );
 6197    "});
 6198    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6199
 6200    // Paste it on a line with a lower indent level
 6201    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6202    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6203    cx.assert_editor_state(indoc! {"
 6204        const a: B = (
 6205            c(),
 6206            something(
 6207                e,
 6208                f
 6209            )
 6210        );
 6211        g(
 6212            e,
 6213            f
 6214"});
 6215}
 6216
 6217#[gpui::test]
 6218async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6219    init_test(cx, |_| {});
 6220
 6221    cx.write_to_clipboard(ClipboardItem::new_string(
 6222        "    d(\n        e\n    );\n".into(),
 6223    ));
 6224
 6225    let mut cx = EditorTestContext::new(cx).await;
 6226    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6227
 6228    cx.set_state(indoc! {"
 6229        fn a() {
 6230            b();
 6231            if c() {
 6232                ˇ
 6233            }
 6234        }
 6235    "});
 6236
 6237    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6238    cx.assert_editor_state(indoc! {"
 6239        fn a() {
 6240            b();
 6241            if c() {
 6242                d(
 6243                    e
 6244                );
 6245        ˇ
 6246            }
 6247        }
 6248    "});
 6249
 6250    cx.set_state(indoc! {"
 6251        fn a() {
 6252            b();
 6253            ˇ
 6254        }
 6255    "});
 6256
 6257    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6258    cx.assert_editor_state(indoc! {"
 6259        fn a() {
 6260            b();
 6261            d(
 6262                e
 6263            );
 6264        ˇ
 6265        }
 6266    "});
 6267}
 6268
 6269#[gpui::test]
 6270fn test_select_all(cx: &mut TestAppContext) {
 6271    init_test(cx, |_| {});
 6272
 6273    let editor = cx.add_window(|window, cx| {
 6274        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6275        build_editor(buffer, window, cx)
 6276    });
 6277    _ = editor.update(cx, |editor, window, cx| {
 6278        editor.select_all(&SelectAll, window, cx);
 6279        assert_eq!(
 6280            editor.selections.display_ranges(cx),
 6281            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6282        );
 6283    });
 6284}
 6285
 6286#[gpui::test]
 6287fn test_select_line(cx: &mut TestAppContext) {
 6288    init_test(cx, |_| {});
 6289
 6290    let editor = cx.add_window(|window, cx| {
 6291        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6292        build_editor(buffer, window, cx)
 6293    });
 6294    _ = editor.update(cx, |editor, window, cx| {
 6295        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6296            s.select_display_ranges([
 6297                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6298                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6299                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6300                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6301            ])
 6302        });
 6303        editor.select_line(&SelectLine, window, cx);
 6304        assert_eq!(
 6305            editor.selections.display_ranges(cx),
 6306            vec![
 6307                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6308                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6309            ]
 6310        );
 6311    });
 6312
 6313    _ = editor.update(cx, |editor, window, cx| {
 6314        editor.select_line(&SelectLine, window, cx);
 6315        assert_eq!(
 6316            editor.selections.display_ranges(cx),
 6317            vec![
 6318                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6319                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6320            ]
 6321        );
 6322    });
 6323
 6324    _ = editor.update(cx, |editor, window, cx| {
 6325        editor.select_line(&SelectLine, window, cx);
 6326        assert_eq!(
 6327            editor.selections.display_ranges(cx),
 6328            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6329        );
 6330    });
 6331}
 6332
 6333#[gpui::test]
 6334async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6335    init_test(cx, |_| {});
 6336    let mut cx = EditorTestContext::new(cx).await;
 6337
 6338    #[track_caller]
 6339    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6340        cx.set_state(initial_state);
 6341        cx.update_editor(|e, window, cx| {
 6342            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6343        });
 6344        cx.assert_editor_state(expected_state);
 6345    }
 6346
 6347    // Selection starts and ends at the middle of lines, left-to-right
 6348    test(
 6349        &mut cx,
 6350        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6351        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6352    );
 6353    // Same thing, right-to-left
 6354    test(
 6355        &mut cx,
 6356        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6357        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6358    );
 6359
 6360    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6361    test(
 6362        &mut cx,
 6363        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6364        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6365    );
 6366    // Same thing, right-to-left
 6367    test(
 6368        &mut cx,
 6369        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6370        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6371    );
 6372
 6373    // Whole buffer, left-to-right, last line ends with newline
 6374    test(
 6375        &mut cx,
 6376        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6377        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6378    );
 6379    // Same thing, right-to-left
 6380    test(
 6381        &mut cx,
 6382        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6383        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6384    );
 6385
 6386    // Starts at the end of a line, ends at the start of another
 6387    test(
 6388        &mut cx,
 6389        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6390        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6391    );
 6392}
 6393
 6394#[gpui::test]
 6395async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6396    init_test(cx, |_| {});
 6397
 6398    let editor = cx.add_window(|window, cx| {
 6399        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6400        build_editor(buffer, window, cx)
 6401    });
 6402
 6403    // setup
 6404    _ = editor.update(cx, |editor, window, cx| {
 6405        editor.fold_creases(
 6406            vec![
 6407                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6408                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6409                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6410            ],
 6411            true,
 6412            window,
 6413            cx,
 6414        );
 6415        assert_eq!(
 6416            editor.display_text(cx),
 6417            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6418        );
 6419    });
 6420
 6421    _ = editor.update(cx, |editor, window, cx| {
 6422        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6423            s.select_display_ranges([
 6424                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6425                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6426                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6427                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6428            ])
 6429        });
 6430        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6431        assert_eq!(
 6432            editor.display_text(cx),
 6433            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6434        );
 6435    });
 6436    EditorTestContext::for_editor(editor, cx)
 6437        .await
 6438        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6439
 6440    _ = editor.update(cx, |editor, window, cx| {
 6441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6442            s.select_display_ranges([
 6443                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6444            ])
 6445        });
 6446        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6447        assert_eq!(
 6448            editor.display_text(cx),
 6449            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6450        );
 6451        assert_eq!(
 6452            editor.selections.display_ranges(cx),
 6453            [
 6454                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6455                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6456                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6457                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6458                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6459                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6460                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6461            ]
 6462        );
 6463    });
 6464    EditorTestContext::for_editor(editor, cx)
 6465        .await
 6466        .assert_editor_state(
 6467            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6468        );
 6469}
 6470
 6471#[gpui::test]
 6472async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6473    init_test(cx, |_| {});
 6474
 6475    let mut cx = EditorTestContext::new(cx).await;
 6476
 6477    cx.set_state(indoc!(
 6478        r#"abc
 6479           defˇghi
 6480
 6481           jk
 6482           nlmo
 6483           "#
 6484    ));
 6485
 6486    cx.update_editor(|editor, window, cx| {
 6487        editor.add_selection_above(&Default::default(), window, cx);
 6488    });
 6489
 6490    cx.assert_editor_state(indoc!(
 6491        r#"abcˇ
 6492           defˇghi
 6493
 6494           jk
 6495           nlmo
 6496           "#
 6497    ));
 6498
 6499    cx.update_editor(|editor, window, cx| {
 6500        editor.add_selection_above(&Default::default(), window, cx);
 6501    });
 6502
 6503    cx.assert_editor_state(indoc!(
 6504        r#"abcˇ
 6505            defˇghi
 6506
 6507            jk
 6508            nlmo
 6509            "#
 6510    ));
 6511
 6512    cx.update_editor(|editor, window, cx| {
 6513        editor.add_selection_below(&Default::default(), window, cx);
 6514    });
 6515
 6516    cx.assert_editor_state(indoc!(
 6517        r#"abc
 6518           defˇghi
 6519
 6520           jk
 6521           nlmo
 6522           "#
 6523    ));
 6524
 6525    cx.update_editor(|editor, window, cx| {
 6526        editor.undo_selection(&Default::default(), window, cx);
 6527    });
 6528
 6529    cx.assert_editor_state(indoc!(
 6530        r#"abcˇ
 6531           defˇghi
 6532
 6533           jk
 6534           nlmo
 6535           "#
 6536    ));
 6537
 6538    cx.update_editor(|editor, window, cx| {
 6539        editor.redo_selection(&Default::default(), window, cx);
 6540    });
 6541
 6542    cx.assert_editor_state(indoc!(
 6543        r#"abc
 6544           defˇghi
 6545
 6546           jk
 6547           nlmo
 6548           "#
 6549    ));
 6550
 6551    cx.update_editor(|editor, window, cx| {
 6552        editor.add_selection_below(&Default::default(), window, cx);
 6553    });
 6554
 6555    cx.assert_editor_state(indoc!(
 6556        r#"abc
 6557           defˇghi
 6558           ˇ
 6559           jk
 6560           nlmo
 6561           "#
 6562    ));
 6563
 6564    cx.update_editor(|editor, window, cx| {
 6565        editor.add_selection_below(&Default::default(), window, cx);
 6566    });
 6567
 6568    cx.assert_editor_state(indoc!(
 6569        r#"abc
 6570           defˇghi
 6571           ˇ
 6572           jkˇ
 6573           nlmo
 6574           "#
 6575    ));
 6576
 6577    cx.update_editor(|editor, window, cx| {
 6578        editor.add_selection_below(&Default::default(), window, cx);
 6579    });
 6580
 6581    cx.assert_editor_state(indoc!(
 6582        r#"abc
 6583           defˇghi
 6584           ˇ
 6585           jkˇ
 6586           nlmˇo
 6587           "#
 6588    ));
 6589
 6590    cx.update_editor(|editor, window, cx| {
 6591        editor.add_selection_below(&Default::default(), window, cx);
 6592    });
 6593
 6594    cx.assert_editor_state(indoc!(
 6595        r#"abc
 6596           defˇghi
 6597           ˇ
 6598           jkˇ
 6599           nlmˇo
 6600           ˇ"#
 6601    ));
 6602
 6603    // change selections
 6604    cx.set_state(indoc!(
 6605        r#"abc
 6606           def«ˇg»hi
 6607
 6608           jk
 6609           nlmo
 6610           "#
 6611    ));
 6612
 6613    cx.update_editor(|editor, window, cx| {
 6614        editor.add_selection_below(&Default::default(), window, cx);
 6615    });
 6616
 6617    cx.assert_editor_state(indoc!(
 6618        r#"abc
 6619           def«ˇg»hi
 6620
 6621           jk
 6622           nlm«ˇo»
 6623           "#
 6624    ));
 6625
 6626    cx.update_editor(|editor, window, cx| {
 6627        editor.add_selection_below(&Default::default(), window, cx);
 6628    });
 6629
 6630    cx.assert_editor_state(indoc!(
 6631        r#"abc
 6632           def«ˇg»hi
 6633
 6634           jk
 6635           nlm«ˇo»
 6636           "#
 6637    ));
 6638
 6639    cx.update_editor(|editor, window, cx| {
 6640        editor.add_selection_above(&Default::default(), window, cx);
 6641    });
 6642
 6643    cx.assert_editor_state(indoc!(
 6644        r#"abc
 6645           def«ˇg»hi
 6646
 6647           jk
 6648           nlmo
 6649           "#
 6650    ));
 6651
 6652    cx.update_editor(|editor, window, cx| {
 6653        editor.add_selection_above(&Default::default(), window, cx);
 6654    });
 6655
 6656    cx.assert_editor_state(indoc!(
 6657        r#"abc
 6658           def«ˇg»hi
 6659
 6660           jk
 6661           nlmo
 6662           "#
 6663    ));
 6664
 6665    // Change selections again
 6666    cx.set_state(indoc!(
 6667        r#"a«bc
 6668           defgˇ»hi
 6669
 6670           jk
 6671           nlmo
 6672           "#
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.add_selection_below(&Default::default(), window, cx);
 6677    });
 6678
 6679    cx.assert_editor_state(indoc!(
 6680        r#"a«bcˇ»
 6681           d«efgˇ»hi
 6682
 6683           j«kˇ»
 6684           nlmo
 6685           "#
 6686    ));
 6687
 6688    cx.update_editor(|editor, window, cx| {
 6689        editor.add_selection_below(&Default::default(), window, cx);
 6690    });
 6691    cx.assert_editor_state(indoc!(
 6692        r#"a«bcˇ»
 6693           d«efgˇ»hi
 6694
 6695           j«kˇ»
 6696           n«lmoˇ»
 6697           "#
 6698    ));
 6699    cx.update_editor(|editor, window, cx| {
 6700        editor.add_selection_above(&Default::default(), window, cx);
 6701    });
 6702
 6703    cx.assert_editor_state(indoc!(
 6704        r#"a«bcˇ»
 6705           d«efgˇ»hi
 6706
 6707           j«kˇ»
 6708           nlmo
 6709           "#
 6710    ));
 6711
 6712    // Change selections again
 6713    cx.set_state(indoc!(
 6714        r#"abc
 6715           d«ˇefghi
 6716
 6717           jk
 6718           nlm»o
 6719           "#
 6720    ));
 6721
 6722    cx.update_editor(|editor, window, cx| {
 6723        editor.add_selection_above(&Default::default(), window, cx);
 6724    });
 6725
 6726    cx.assert_editor_state(indoc!(
 6727        r#"a«ˇbc»
 6728           d«ˇef»ghi
 6729
 6730           j«ˇk»
 6731           n«ˇlm»o
 6732           "#
 6733    ));
 6734
 6735    cx.update_editor(|editor, window, cx| {
 6736        editor.add_selection_below(&Default::default(), window, cx);
 6737    });
 6738
 6739    cx.assert_editor_state(indoc!(
 6740        r#"abc
 6741           d«ˇef»ghi
 6742
 6743           j«ˇk»
 6744           n«ˇlm»o
 6745           "#
 6746    ));
 6747}
 6748
 6749#[gpui::test]
 6750async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6751    init_test(cx, |_| {});
 6752    let mut cx = EditorTestContext::new(cx).await;
 6753
 6754    cx.set_state(indoc!(
 6755        r#"line onˇe
 6756           liˇne two
 6757           line three
 6758           line four"#
 6759    ));
 6760
 6761    cx.update_editor(|editor, window, cx| {
 6762        editor.add_selection_below(&Default::default(), window, cx);
 6763    });
 6764
 6765    // test multiple cursors expand in the same direction
 6766    cx.assert_editor_state(indoc!(
 6767        r#"line onˇe
 6768           liˇne twˇo
 6769           liˇne three
 6770           line four"#
 6771    ));
 6772
 6773    cx.update_editor(|editor, window, cx| {
 6774        editor.add_selection_below(&Default::default(), window, cx);
 6775    });
 6776
 6777    cx.update_editor(|editor, window, cx| {
 6778        editor.add_selection_below(&Default::default(), window, cx);
 6779    });
 6780
 6781    // test multiple cursors expand below overflow
 6782    cx.assert_editor_state(indoc!(
 6783        r#"line onˇe
 6784           liˇne twˇo
 6785           liˇne thˇree
 6786           liˇne foˇur"#
 6787    ));
 6788
 6789    cx.update_editor(|editor, window, cx| {
 6790        editor.add_selection_above(&Default::default(), window, cx);
 6791    });
 6792
 6793    // test multiple cursors retrieves back correctly
 6794    cx.assert_editor_state(indoc!(
 6795        r#"line onˇe
 6796           liˇne twˇo
 6797           liˇne thˇree
 6798           line four"#
 6799    ));
 6800
 6801    cx.update_editor(|editor, window, cx| {
 6802        editor.add_selection_above(&Default::default(), window, cx);
 6803    });
 6804
 6805    cx.update_editor(|editor, window, cx| {
 6806        editor.add_selection_above(&Default::default(), window, cx);
 6807    });
 6808
 6809    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6810    cx.assert_editor_state(indoc!(
 6811        r#"liˇne onˇe
 6812           liˇne two
 6813           line three
 6814           line four"#
 6815    ));
 6816
 6817    cx.update_editor(|editor, window, cx| {
 6818        editor.undo_selection(&Default::default(), window, cx);
 6819    });
 6820
 6821    // test undo
 6822    cx.assert_editor_state(indoc!(
 6823        r#"line onˇe
 6824           liˇne twˇo
 6825           line three
 6826           line four"#
 6827    ));
 6828
 6829    cx.update_editor(|editor, window, cx| {
 6830        editor.redo_selection(&Default::default(), window, cx);
 6831    });
 6832
 6833    // test redo
 6834    cx.assert_editor_state(indoc!(
 6835        r#"liˇne onˇe
 6836           liˇne two
 6837           line three
 6838           line four"#
 6839    ));
 6840
 6841    cx.set_state(indoc!(
 6842        r#"abcd
 6843           ef«ghˇ»
 6844           ijkl
 6845           «mˇ»nop"#
 6846    ));
 6847
 6848    cx.update_editor(|editor, window, cx| {
 6849        editor.add_selection_above(&Default::default(), window, cx);
 6850    });
 6851
 6852    // test multiple selections expand in the same direction
 6853    cx.assert_editor_state(indoc!(
 6854        r#"ab«cdˇ»
 6855           ef«ghˇ»
 6856           «iˇ»jkl
 6857           «mˇ»nop"#
 6858    ));
 6859
 6860    cx.update_editor(|editor, window, cx| {
 6861        editor.add_selection_above(&Default::default(), window, cx);
 6862    });
 6863
 6864    // test multiple selection upward overflow
 6865    cx.assert_editor_state(indoc!(
 6866        r#"ab«cdˇ»
 6867           «eˇ»f«ghˇ»
 6868           «iˇ»jkl
 6869           «mˇ»nop"#
 6870    ));
 6871
 6872    cx.update_editor(|editor, window, cx| {
 6873        editor.add_selection_below(&Default::default(), window, cx);
 6874    });
 6875
 6876    // test multiple selection retrieves back correctly
 6877    cx.assert_editor_state(indoc!(
 6878        r#"abcd
 6879           ef«ghˇ»
 6880           «iˇ»jkl
 6881           «mˇ»nop"#
 6882    ));
 6883
 6884    cx.update_editor(|editor, window, cx| {
 6885        editor.add_selection_below(&Default::default(), window, cx);
 6886    });
 6887
 6888    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6889    cx.assert_editor_state(indoc!(
 6890        r#"abcd
 6891           ef«ghˇ»
 6892           ij«klˇ»
 6893           «mˇ»nop"#
 6894    ));
 6895
 6896    cx.update_editor(|editor, window, cx| {
 6897        editor.undo_selection(&Default::default(), window, cx);
 6898    });
 6899
 6900    // test undo
 6901    cx.assert_editor_state(indoc!(
 6902        r#"abcd
 6903           ef«ghˇ»
 6904           «iˇ»jkl
 6905           «mˇ»nop"#
 6906    ));
 6907
 6908    cx.update_editor(|editor, window, cx| {
 6909        editor.redo_selection(&Default::default(), window, cx);
 6910    });
 6911
 6912    // test redo
 6913    cx.assert_editor_state(indoc!(
 6914        r#"abcd
 6915           ef«ghˇ»
 6916           ij«klˇ»
 6917           «mˇ»nop"#
 6918    ));
 6919}
 6920
 6921#[gpui::test]
 6922async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6923    init_test(cx, |_| {});
 6924    let mut cx = EditorTestContext::new(cx).await;
 6925
 6926    cx.set_state(indoc!(
 6927        r#"line onˇe
 6928           liˇne two
 6929           line three
 6930           line four"#
 6931    ));
 6932
 6933    cx.update_editor(|editor, window, cx| {
 6934        editor.add_selection_below(&Default::default(), window, cx);
 6935        editor.add_selection_below(&Default::default(), window, cx);
 6936        editor.add_selection_below(&Default::default(), window, cx);
 6937    });
 6938
 6939    // initial state with two multi cursor groups
 6940    cx.assert_editor_state(indoc!(
 6941        r#"line onˇe
 6942           liˇne twˇo
 6943           liˇne thˇree
 6944           liˇne foˇur"#
 6945    ));
 6946
 6947    // add single cursor in middle - simulate opt click
 6948    cx.update_editor(|editor, window, cx| {
 6949        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6950        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6951        editor.end_selection(window, cx);
 6952    });
 6953
 6954    cx.assert_editor_state(indoc!(
 6955        r#"line onˇe
 6956           liˇne twˇo
 6957           liˇneˇ thˇree
 6958           liˇne foˇur"#
 6959    ));
 6960
 6961    cx.update_editor(|editor, window, cx| {
 6962        editor.add_selection_above(&Default::default(), window, cx);
 6963    });
 6964
 6965    // test new added selection expands above and existing selection shrinks
 6966    cx.assert_editor_state(indoc!(
 6967        r#"line onˇe
 6968           liˇneˇ twˇo
 6969           liˇneˇ thˇree
 6970           line four"#
 6971    ));
 6972
 6973    cx.update_editor(|editor, window, cx| {
 6974        editor.add_selection_above(&Default::default(), window, cx);
 6975    });
 6976
 6977    // test new added selection expands above and existing selection shrinks
 6978    cx.assert_editor_state(indoc!(
 6979        r#"lineˇ onˇe
 6980           liˇneˇ twˇo
 6981           lineˇ three
 6982           line four"#
 6983    ));
 6984
 6985    // intial state with two selection groups
 6986    cx.set_state(indoc!(
 6987        r#"abcd
 6988           ef«ghˇ»
 6989           ijkl
 6990           «mˇ»nop"#
 6991    ));
 6992
 6993    cx.update_editor(|editor, window, cx| {
 6994        editor.add_selection_above(&Default::default(), window, cx);
 6995        editor.add_selection_above(&Default::default(), window, cx);
 6996    });
 6997
 6998    cx.assert_editor_state(indoc!(
 6999        r#"ab«cdˇ»
 7000           «eˇ»f«ghˇ»
 7001           «iˇ»jkl
 7002           «mˇ»nop"#
 7003    ));
 7004
 7005    // add single selection in middle - simulate opt drag
 7006    cx.update_editor(|editor, window, cx| {
 7007        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7008        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7009        editor.update_selection(
 7010            DisplayPoint::new(DisplayRow(2), 4),
 7011            0,
 7012            gpui::Point::<f32>::default(),
 7013            window,
 7014            cx,
 7015        );
 7016        editor.end_selection(window, cx);
 7017    });
 7018
 7019    cx.assert_editor_state(indoc!(
 7020        r#"ab«cdˇ»
 7021           «eˇ»f«ghˇ»
 7022           «iˇ»jk«lˇ»
 7023           «mˇ»nop"#
 7024    ));
 7025
 7026    cx.update_editor(|editor, window, cx| {
 7027        editor.add_selection_below(&Default::default(), window, cx);
 7028    });
 7029
 7030    // test new added selection expands below, others shrinks from above
 7031    cx.assert_editor_state(indoc!(
 7032        r#"abcd
 7033           ef«ghˇ»
 7034           «iˇ»jk«lˇ»
 7035           «mˇ»no«pˇ»"#
 7036    ));
 7037}
 7038
 7039#[gpui::test]
 7040async fn test_select_next(cx: &mut TestAppContext) {
 7041    init_test(cx, |_| {});
 7042
 7043    let mut cx = EditorTestContext::new(cx).await;
 7044    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7045
 7046    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7047        .unwrap();
 7048    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7049
 7050    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7051        .unwrap();
 7052    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7053
 7054    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7055    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7056
 7057    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7058    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7059
 7060    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7061        .unwrap();
 7062    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7063
 7064    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7065        .unwrap();
 7066    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7067
 7068    // Test selection direction should be preserved
 7069    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7070
 7071    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7072        .unwrap();
 7073    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7074}
 7075
 7076#[gpui::test]
 7077async fn test_select_all_matches(cx: &mut TestAppContext) {
 7078    init_test(cx, |_| {});
 7079
 7080    let mut cx = EditorTestContext::new(cx).await;
 7081
 7082    // Test caret-only selections
 7083    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7084    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7085        .unwrap();
 7086    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7087
 7088    // Test left-to-right selections
 7089    cx.set_state("abc\n«abcˇ»\nabc");
 7090    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7091        .unwrap();
 7092    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7093
 7094    // Test right-to-left selections
 7095    cx.set_state("abc\n«ˇabc»\nabc");
 7096    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7097        .unwrap();
 7098    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7099
 7100    // Test selecting whitespace with caret selection
 7101    cx.set_state("abc\nˇ   abc\nabc");
 7102    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7103        .unwrap();
 7104    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7105
 7106    // Test selecting whitespace with left-to-right selection
 7107    cx.set_state("abc\n«ˇ  »abc\nabc");
 7108    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7109        .unwrap();
 7110    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7111
 7112    // Test no matches with right-to-left selection
 7113    cx.set_state("abc\n«  ˇ»abc\nabc");
 7114    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7115        .unwrap();
 7116    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7117
 7118    // Test with a single word and clip_at_line_ends=true (#29823)
 7119    cx.set_state("aˇbc");
 7120    cx.update_editor(|e, window, cx| {
 7121        e.set_clip_at_line_ends(true, cx);
 7122        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7123        e.set_clip_at_line_ends(false, cx);
 7124    });
 7125    cx.assert_editor_state("«abcˇ»");
 7126}
 7127
 7128#[gpui::test]
 7129async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7130    init_test(cx, |_| {});
 7131
 7132    let mut cx = EditorTestContext::new(cx).await;
 7133
 7134    let large_body_1 = "\nd".repeat(200);
 7135    let large_body_2 = "\ne".repeat(200);
 7136
 7137    cx.set_state(&format!(
 7138        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7139    ));
 7140    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7141        let scroll_position = editor.scroll_position(cx);
 7142        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7143        scroll_position
 7144    });
 7145
 7146    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7147        .unwrap();
 7148    cx.assert_editor_state(&format!(
 7149        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7150    ));
 7151    let scroll_position_after_selection =
 7152        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7153    assert_eq!(
 7154        initial_scroll_position, scroll_position_after_selection,
 7155        "Scroll position should not change after selecting all matches"
 7156    );
 7157}
 7158
 7159#[gpui::test]
 7160async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7161    init_test(cx, |_| {});
 7162
 7163    let mut cx = EditorLspTestContext::new_rust(
 7164        lsp::ServerCapabilities {
 7165            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7166            ..Default::default()
 7167        },
 7168        cx,
 7169    )
 7170    .await;
 7171
 7172    cx.set_state(indoc! {"
 7173        line 1
 7174        line 2
 7175        linˇe 3
 7176        line 4
 7177        line 5
 7178    "});
 7179
 7180    // Make an edit
 7181    cx.update_editor(|editor, window, cx| {
 7182        editor.handle_input("X", window, cx);
 7183    });
 7184
 7185    // Move cursor to a different position
 7186    cx.update_editor(|editor, window, cx| {
 7187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7188            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7189        });
 7190    });
 7191
 7192    cx.assert_editor_state(indoc! {"
 7193        line 1
 7194        line 2
 7195        linXe 3
 7196        line 4
 7197        liˇne 5
 7198    "});
 7199
 7200    cx.lsp
 7201        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7202            Ok(Some(vec![lsp::TextEdit::new(
 7203                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7204                "PREFIX ".to_string(),
 7205            )]))
 7206        });
 7207
 7208    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7209        .unwrap()
 7210        .await
 7211        .unwrap();
 7212
 7213    cx.assert_editor_state(indoc! {"
 7214        PREFIX line 1
 7215        line 2
 7216        linXe 3
 7217        line 4
 7218        liˇne 5
 7219    "});
 7220
 7221    // Undo formatting
 7222    cx.update_editor(|editor, window, cx| {
 7223        editor.undo(&Default::default(), window, cx);
 7224    });
 7225
 7226    // Verify cursor moved back to position after edit
 7227    cx.assert_editor_state(indoc! {"
 7228        line 1
 7229        line 2
 7230        linXˇe 3
 7231        line 4
 7232        line 5
 7233    "});
 7234}
 7235
 7236#[gpui::test]
 7237async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7238    init_test(cx, |_| {});
 7239
 7240    let mut cx = EditorTestContext::new(cx).await;
 7241
 7242    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7243    cx.update_editor(|editor, window, cx| {
 7244        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7245    });
 7246
 7247    cx.set_state(indoc! {"
 7248        line 1
 7249        line 2
 7250        linˇe 3
 7251        line 4
 7252        line 5
 7253        line 6
 7254        line 7
 7255        line 8
 7256        line 9
 7257        line 10
 7258    "});
 7259
 7260    let snapshot = cx.buffer_snapshot();
 7261    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7262
 7263    cx.update(|_, cx| {
 7264        provider.update(cx, |provider, _| {
 7265            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7266                id: None,
 7267                edits: vec![(edit_position..edit_position, "X".into())],
 7268                edit_preview: None,
 7269            }))
 7270        })
 7271    });
 7272
 7273    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7274    cx.update_editor(|editor, window, cx| {
 7275        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7276    });
 7277
 7278    cx.assert_editor_state(indoc! {"
 7279        line 1
 7280        line 2
 7281        lineXˇ 3
 7282        line 4
 7283        line 5
 7284        line 6
 7285        line 7
 7286        line 8
 7287        line 9
 7288        line 10
 7289    "});
 7290
 7291    cx.update_editor(|editor, window, cx| {
 7292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7293            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7294        });
 7295    });
 7296
 7297    cx.assert_editor_state(indoc! {"
 7298        line 1
 7299        line 2
 7300        lineX 3
 7301        line 4
 7302        line 5
 7303        line 6
 7304        line 7
 7305        line 8
 7306        line 9
 7307        liˇne 10
 7308    "});
 7309
 7310    cx.update_editor(|editor, window, cx| {
 7311        editor.undo(&Default::default(), window, cx);
 7312    });
 7313
 7314    cx.assert_editor_state(indoc! {"
 7315        line 1
 7316        line 2
 7317        lineˇ 3
 7318        line 4
 7319        line 5
 7320        line 6
 7321        line 7
 7322        line 8
 7323        line 9
 7324        line 10
 7325    "});
 7326}
 7327
 7328#[gpui::test]
 7329async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7330    init_test(cx, |_| {});
 7331
 7332    let mut cx = EditorTestContext::new(cx).await;
 7333    cx.set_state(
 7334        r#"let foo = 2;
 7335lˇet foo = 2;
 7336let fooˇ = 2;
 7337let foo = 2;
 7338let foo = ˇ2;"#,
 7339    );
 7340
 7341    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7342        .unwrap();
 7343    cx.assert_editor_state(
 7344        r#"let foo = 2;
 7345«letˇ» foo = 2;
 7346let «fooˇ» = 2;
 7347let foo = 2;
 7348let foo = «2ˇ»;"#,
 7349    );
 7350
 7351    // noop for multiple selections with different contents
 7352    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7353        .unwrap();
 7354    cx.assert_editor_state(
 7355        r#"let foo = 2;
 7356«letˇ» foo = 2;
 7357let «fooˇ» = 2;
 7358let foo = 2;
 7359let foo = «2ˇ»;"#,
 7360    );
 7361
 7362    // Test last selection direction should be preserved
 7363    cx.set_state(
 7364        r#"let foo = 2;
 7365let foo = 2;
 7366let «fooˇ» = 2;
 7367let «ˇfoo» = 2;
 7368let foo = 2;"#,
 7369    );
 7370
 7371    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7372        .unwrap();
 7373    cx.assert_editor_state(
 7374        r#"let foo = 2;
 7375let foo = 2;
 7376let «fooˇ» = 2;
 7377let «ˇfoo» = 2;
 7378let «ˇfoo» = 2;"#,
 7379    );
 7380}
 7381
 7382#[gpui::test]
 7383async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7384    init_test(cx, |_| {});
 7385
 7386    let mut cx =
 7387        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7388
 7389    cx.assert_editor_state(indoc! {"
 7390        ˇbbb
 7391        ccc
 7392
 7393        bbb
 7394        ccc
 7395        "});
 7396    cx.dispatch_action(SelectPrevious::default());
 7397    cx.assert_editor_state(indoc! {"
 7398                «bbbˇ»
 7399                ccc
 7400
 7401                bbb
 7402                ccc
 7403                "});
 7404    cx.dispatch_action(SelectPrevious::default());
 7405    cx.assert_editor_state(indoc! {"
 7406                «bbbˇ»
 7407                ccc
 7408
 7409                «bbbˇ»
 7410                ccc
 7411                "});
 7412}
 7413
 7414#[gpui::test]
 7415async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7416    init_test(cx, |_| {});
 7417
 7418    let mut cx = EditorTestContext::new(cx).await;
 7419    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7420
 7421    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7422        .unwrap();
 7423    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7424
 7425    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7426        .unwrap();
 7427    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7428
 7429    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7430    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7431
 7432    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7433    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7434
 7435    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7436        .unwrap();
 7437    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7438
 7439    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7440        .unwrap();
 7441    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7442}
 7443
 7444#[gpui::test]
 7445async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7446    init_test(cx, |_| {});
 7447
 7448    let mut cx = EditorTestContext::new(cx).await;
 7449    cx.set_state("");
 7450
 7451    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7452        .unwrap();
 7453    cx.assert_editor_state("«aˇ»");
 7454    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7455        .unwrap();
 7456    cx.assert_editor_state("«aˇ»");
 7457}
 7458
 7459#[gpui::test]
 7460async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7461    init_test(cx, |_| {});
 7462
 7463    let mut cx = EditorTestContext::new(cx).await;
 7464    cx.set_state(
 7465        r#"let foo = 2;
 7466lˇet foo = 2;
 7467let fooˇ = 2;
 7468let foo = 2;
 7469let foo = ˇ2;"#,
 7470    );
 7471
 7472    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7473        .unwrap();
 7474    cx.assert_editor_state(
 7475        r#"let foo = 2;
 7476«letˇ» foo = 2;
 7477let «fooˇ» = 2;
 7478let foo = 2;
 7479let foo = «2ˇ»;"#,
 7480    );
 7481
 7482    // noop for multiple selections with different contents
 7483    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7484        .unwrap();
 7485    cx.assert_editor_state(
 7486        r#"let foo = 2;
 7487«letˇ» foo = 2;
 7488let «fooˇ» = 2;
 7489let foo = 2;
 7490let foo = «2ˇ»;"#,
 7491    );
 7492}
 7493
 7494#[gpui::test]
 7495async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7496    init_test(cx, |_| {});
 7497
 7498    let mut cx = EditorTestContext::new(cx).await;
 7499    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7500
 7501    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7502        .unwrap();
 7503    // selection direction is preserved
 7504    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7505
 7506    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7507        .unwrap();
 7508    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7509
 7510    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7511    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7512
 7513    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7514    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7515
 7516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7517        .unwrap();
 7518    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7519
 7520    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7521        .unwrap();
 7522    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7523}
 7524
 7525#[gpui::test]
 7526async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7527    init_test(cx, |_| {});
 7528
 7529    let language = Arc::new(Language::new(
 7530        LanguageConfig::default(),
 7531        Some(tree_sitter_rust::LANGUAGE.into()),
 7532    ));
 7533
 7534    let text = r#"
 7535        use mod1::mod2::{mod3, mod4};
 7536
 7537        fn fn_1(param1: bool, param2: &str) {
 7538            let var1 = "text";
 7539        }
 7540    "#
 7541    .unindent();
 7542
 7543    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7544    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7545    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7546
 7547    editor
 7548        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7549        .await;
 7550
 7551    editor.update_in(cx, |editor, window, cx| {
 7552        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7553            s.select_display_ranges([
 7554                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7555                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7556                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7557            ]);
 7558        });
 7559        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7560    });
 7561    editor.update(cx, |editor, cx| {
 7562        assert_text_with_selections(
 7563            editor,
 7564            indoc! {r#"
 7565                use mod1::mod2::{mod3, «mod4ˇ»};
 7566
 7567                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7568                    let var1 = "«ˇtext»";
 7569                }
 7570            "#},
 7571            cx,
 7572        );
 7573    });
 7574
 7575    editor.update_in(cx, |editor, window, cx| {
 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    assert_eq!(
 7596        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7597        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7598    );
 7599
 7600    // Trying to expand the selected syntax node one more time has no effect.
 7601    editor.update_in(cx, |editor, window, cx| {
 7602        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7603    });
 7604    assert_eq!(
 7605        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7606        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7607    );
 7608
 7609    editor.update_in(cx, |editor, window, cx| {
 7610        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7611    });
 7612    editor.update(cx, |editor, cx| {
 7613        assert_text_with_selections(
 7614            editor,
 7615            indoc! {r#"
 7616                use mod1::mod2::«{mod3, mod4}ˇ»;
 7617
 7618                «ˇfn fn_1(param1: bool, param2: &str) {
 7619                    let var1 = "text";
 7620 7621            "#},
 7622            cx,
 7623        );
 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, mo«ˇ»d4};
 7651
 7652                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7653                    let var1 = "te«ˇ»xt";
 7654                }
 7655            "#},
 7656            cx,
 7657        );
 7658    });
 7659
 7660    // Trying to shrink the selected syntax node one more time has no effect.
 7661    editor.update_in(cx, |editor, window, cx| {
 7662        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7663    });
 7664    editor.update_in(cx, |editor, _, cx| {
 7665        assert_text_with_selections(
 7666            editor,
 7667            indoc! {r#"
 7668                use mod1::mod2::{mod3, mo«ˇ»d4};
 7669
 7670                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7671                    let var1 = "te«ˇ»xt";
 7672                }
 7673            "#},
 7674            cx,
 7675        );
 7676    });
 7677
 7678    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7679    // a fold.
 7680    editor.update_in(cx, |editor, window, cx| {
 7681        editor.fold_creases(
 7682            vec![
 7683                Crease::simple(
 7684                    Point::new(0, 21)..Point::new(0, 24),
 7685                    FoldPlaceholder::test(),
 7686                ),
 7687                Crease::simple(
 7688                    Point::new(3, 20)..Point::new(3, 22),
 7689                    FoldPlaceholder::test(),
 7690                ),
 7691            ],
 7692            true,
 7693            window,
 7694            cx,
 7695        );
 7696        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7697    });
 7698    editor.update(cx, |editor, cx| {
 7699        assert_text_with_selections(
 7700            editor,
 7701            indoc! {r#"
 7702                use mod1::mod2::«{mod3, mod4}ˇ»;
 7703
 7704                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7705                    let var1 = "«ˇtext»";
 7706                }
 7707            "#},
 7708            cx,
 7709        );
 7710    });
 7711}
 7712
 7713#[gpui::test]
 7714async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7715    init_test(cx, |_| {});
 7716
 7717    let language = Arc::new(Language::new(
 7718        LanguageConfig::default(),
 7719        Some(tree_sitter_rust::LANGUAGE.into()),
 7720    ));
 7721
 7722    let text = "let a = 2;";
 7723
 7724    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7725    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7726    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7727
 7728    editor
 7729        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7730        .await;
 7731
 7732    // Test case 1: Cursor at end of word
 7733    editor.update_in(cx, |editor, window, cx| {
 7734        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7735            s.select_display_ranges([
 7736                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7737            ]);
 7738        });
 7739    });
 7740    editor.update(cx, |editor, cx| {
 7741        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7742    });
 7743    editor.update_in(cx, |editor, window, cx| {
 7744        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7745    });
 7746    editor.update(cx, |editor, cx| {
 7747        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7748    });
 7749    editor.update_in(cx, |editor, window, cx| {
 7750        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7751    });
 7752    editor.update(cx, |editor, cx| {
 7753        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7754    });
 7755
 7756    // Test case 2: Cursor at end of statement
 7757    editor.update_in(cx, |editor, window, cx| {
 7758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7759            s.select_display_ranges([
 7760                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7761            ]);
 7762        });
 7763    });
 7764    editor.update(cx, |editor, cx| {
 7765        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7766    });
 7767    editor.update_in(cx, |editor, window, cx| {
 7768        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7769    });
 7770    editor.update(cx, |editor, cx| {
 7771        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7772    });
 7773}
 7774
 7775#[gpui::test]
 7776async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7777    init_test(cx, |_| {});
 7778
 7779    let language = Arc::new(Language::new(
 7780        LanguageConfig::default(),
 7781        Some(tree_sitter_rust::LANGUAGE.into()),
 7782    ));
 7783
 7784    let text = r#"
 7785        use mod1::mod2::{mod3, mod4};
 7786
 7787        fn fn_1(param1: bool, param2: &str) {
 7788            let var1 = "hello world";
 7789        }
 7790    "#
 7791    .unindent();
 7792
 7793    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7794    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7795    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7796
 7797    editor
 7798        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7799        .await;
 7800
 7801    // Test 1: Cursor on a letter of a string word
 7802    editor.update_in(cx, |editor, window, cx| {
 7803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7804            s.select_display_ranges([
 7805                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7806            ]);
 7807        });
 7808    });
 7809    editor.update_in(cx, |editor, window, cx| {
 7810        assert_text_with_selections(
 7811            editor,
 7812            indoc! {r#"
 7813                use mod1::mod2::{mod3, mod4};
 7814
 7815                fn fn_1(param1: bool, param2: &str) {
 7816                    let var1 = "hˇello world";
 7817                }
 7818            "#},
 7819            cx,
 7820        );
 7821        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7822        assert_text_with_selections(
 7823            editor,
 7824            indoc! {r#"
 7825                use mod1::mod2::{mod3, mod4};
 7826
 7827                fn fn_1(param1: bool, param2: &str) {
 7828                    let var1 = "«ˇhello» world";
 7829                }
 7830            "#},
 7831            cx,
 7832        );
 7833    });
 7834
 7835    // Test 2: Partial selection within a word
 7836    editor.update_in(cx, |editor, window, cx| {
 7837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7838            s.select_display_ranges([
 7839                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7840            ]);
 7841        });
 7842    });
 7843    editor.update_in(cx, |editor, window, cx| {
 7844        assert_text_with_selections(
 7845            editor,
 7846            indoc! {r#"
 7847                use mod1::mod2::{mod3, mod4};
 7848
 7849                fn fn_1(param1: bool, param2: &str) {
 7850                    let var1 = "h«elˇ»lo world";
 7851                }
 7852            "#},
 7853            cx,
 7854        );
 7855        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7856        assert_text_with_selections(
 7857            editor,
 7858            indoc! {r#"
 7859                use mod1::mod2::{mod3, mod4};
 7860
 7861                fn fn_1(param1: bool, param2: &str) {
 7862                    let var1 = "«ˇhello» world";
 7863                }
 7864            "#},
 7865            cx,
 7866        );
 7867    });
 7868
 7869    // Test 3: Complete word already selected
 7870    editor.update_in(cx, |editor, window, cx| {
 7871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7872            s.select_display_ranges([
 7873                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7874            ]);
 7875        });
 7876    });
 7877    editor.update_in(cx, |editor, window, cx| {
 7878        assert_text_with_selections(
 7879            editor,
 7880            indoc! {r#"
 7881                use mod1::mod2::{mod3, mod4};
 7882
 7883                fn fn_1(param1: bool, param2: &str) {
 7884                    let var1 = "«helloˇ» world";
 7885                }
 7886            "#},
 7887            cx,
 7888        );
 7889        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7890        assert_text_with_selections(
 7891            editor,
 7892            indoc! {r#"
 7893                use mod1::mod2::{mod3, mod4};
 7894
 7895                fn fn_1(param1: bool, param2: &str) {
 7896                    let var1 = "«hello worldˇ»";
 7897                }
 7898            "#},
 7899            cx,
 7900        );
 7901    });
 7902
 7903    // Test 4: Selection spanning across words
 7904    editor.update_in(cx, |editor, window, cx| {
 7905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7906            s.select_display_ranges([
 7907                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7908            ]);
 7909        });
 7910    });
 7911    editor.update_in(cx, |editor, window, cx| {
 7912        assert_text_with_selections(
 7913            editor,
 7914            indoc! {r#"
 7915                use mod1::mod2::{mod3, mod4};
 7916
 7917                fn fn_1(param1: bool, param2: &str) {
 7918                    let var1 = "hel«lo woˇ»rld";
 7919                }
 7920            "#},
 7921            cx,
 7922        );
 7923        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7924        assert_text_with_selections(
 7925            editor,
 7926            indoc! {r#"
 7927                use mod1::mod2::{mod3, mod4};
 7928
 7929                fn fn_1(param1: bool, param2: &str) {
 7930                    let var1 = "«ˇhello world»";
 7931                }
 7932            "#},
 7933            cx,
 7934        );
 7935    });
 7936
 7937    // Test 5: Expansion beyond string
 7938    editor.update_in(cx, |editor, window, cx| {
 7939        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 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
 7955#[gpui::test]
 7956async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7957    init_test(cx, |_| {});
 7958
 7959    let base_text = r#"
 7960        impl A {
 7961            // this is an uncommitted comment
 7962
 7963            fn b() {
 7964                c();
 7965            }
 7966
 7967            // this is another uncommitted comment
 7968
 7969            fn d() {
 7970                // e
 7971                // f
 7972            }
 7973        }
 7974
 7975        fn g() {
 7976            // h
 7977        }
 7978    "#
 7979    .unindent();
 7980
 7981    let text = r#"
 7982        ˇimpl A {
 7983
 7984            fn b() {
 7985                c();
 7986            }
 7987
 7988            fn d() {
 7989                // e
 7990                // f
 7991            }
 7992        }
 7993
 7994        fn g() {
 7995            // h
 7996        }
 7997    "#
 7998    .unindent();
 7999
 8000    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8001    cx.set_state(&text);
 8002    cx.set_head_text(&base_text);
 8003    cx.update_editor(|editor, window, cx| {
 8004        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8005    });
 8006
 8007    cx.assert_state_with_diff(
 8008        "
 8009        ˇimpl A {
 8010      -     // this is an uncommitted comment
 8011
 8012            fn b() {
 8013                c();
 8014            }
 8015
 8016      -     // this is another uncommitted comment
 8017      -
 8018            fn d() {
 8019                // e
 8020                // f
 8021            }
 8022        }
 8023
 8024        fn g() {
 8025            // h
 8026        }
 8027    "
 8028        .unindent(),
 8029    );
 8030
 8031    let expected_display_text = "
 8032        impl A {
 8033            // this is an uncommitted comment
 8034
 8035            fn b() {
 8036 8037            }
 8038
 8039            // this is another uncommitted comment
 8040
 8041            fn d() {
 8042 8043            }
 8044        }
 8045
 8046        fn g() {
 8047 8048        }
 8049        "
 8050    .unindent();
 8051
 8052    cx.update_editor(|editor, window, cx| {
 8053        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8054        assert_eq!(editor.display_text(cx), expected_display_text);
 8055    });
 8056}
 8057
 8058#[gpui::test]
 8059async fn test_autoindent(cx: &mut TestAppContext) {
 8060    init_test(cx, |_| {});
 8061
 8062    let language = Arc::new(
 8063        Language::new(
 8064            LanguageConfig {
 8065                brackets: BracketPairConfig {
 8066                    pairs: vec![
 8067                        BracketPair {
 8068                            start: "{".to_string(),
 8069                            end: "}".to_string(),
 8070                            close: false,
 8071                            surround: false,
 8072                            newline: true,
 8073                        },
 8074                        BracketPair {
 8075                            start: "(".to_string(),
 8076                            end: ")".to_string(),
 8077                            close: false,
 8078                            surround: false,
 8079                            newline: true,
 8080                        },
 8081                    ],
 8082                    ..Default::default()
 8083                },
 8084                ..Default::default()
 8085            },
 8086            Some(tree_sitter_rust::LANGUAGE.into()),
 8087        )
 8088        .with_indents_query(
 8089            r#"
 8090                (_ "(" ")" @end) @indent
 8091                (_ "{" "}" @end) @indent
 8092            "#,
 8093        )
 8094        .unwrap(),
 8095    );
 8096
 8097    let text = "fn a() {}";
 8098
 8099    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8100    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8101    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8102    editor
 8103        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8104        .await;
 8105
 8106    editor.update_in(cx, |editor, window, cx| {
 8107        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8108            s.select_ranges([5..5, 8..8, 9..9])
 8109        });
 8110        editor.newline(&Newline, window, cx);
 8111        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8112        assert_eq!(
 8113            editor.selections.ranges(cx),
 8114            &[
 8115                Point::new(1, 4)..Point::new(1, 4),
 8116                Point::new(3, 4)..Point::new(3, 4),
 8117                Point::new(5, 0)..Point::new(5, 0)
 8118            ]
 8119        );
 8120    });
 8121}
 8122
 8123#[gpui::test]
 8124async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8125    init_test(cx, |_| {});
 8126
 8127    {
 8128        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8129        cx.set_state(indoc! {"
 8130            impl A {
 8131
 8132                fn b() {}
 8133
 8134            «fn c() {
 8135
 8136            }ˇ»
 8137            }
 8138        "});
 8139
 8140        cx.update_editor(|editor, window, cx| {
 8141            editor.autoindent(&Default::default(), window, cx);
 8142        });
 8143
 8144        cx.assert_editor_state(indoc! {"
 8145            impl A {
 8146
 8147                fn b() {}
 8148
 8149                «fn c() {
 8150
 8151                }ˇ»
 8152            }
 8153        "});
 8154    }
 8155
 8156    {
 8157        let mut cx = EditorTestContext::new_multibuffer(
 8158            cx,
 8159            [indoc! { "
 8160                impl A {
 8161                «
 8162                // a
 8163                fn b(){}
 8164                »
 8165                «
 8166                    }
 8167                    fn c(){}
 8168                »
 8169            "}],
 8170        );
 8171
 8172        let buffer = cx.update_editor(|editor, _, cx| {
 8173            let buffer = editor.buffer().update(cx, |buffer, _| {
 8174                buffer.all_buffers().iter().next().unwrap().clone()
 8175            });
 8176            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8177            buffer
 8178        });
 8179
 8180        cx.run_until_parked();
 8181        cx.update_editor(|editor, window, cx| {
 8182            editor.select_all(&Default::default(), window, cx);
 8183            editor.autoindent(&Default::default(), window, cx)
 8184        });
 8185        cx.run_until_parked();
 8186
 8187        cx.update(|_, cx| {
 8188            assert_eq!(
 8189                buffer.read(cx).text(),
 8190                indoc! { "
 8191                    impl A {
 8192
 8193                        // a
 8194                        fn b(){}
 8195
 8196
 8197                    }
 8198                    fn c(){}
 8199
 8200                " }
 8201            )
 8202        });
 8203    }
 8204}
 8205
 8206#[gpui::test]
 8207async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8208    init_test(cx, |_| {});
 8209
 8210    let mut cx = EditorTestContext::new(cx).await;
 8211
 8212    let language = Arc::new(Language::new(
 8213        LanguageConfig {
 8214            brackets: BracketPairConfig {
 8215                pairs: vec![
 8216                    BracketPair {
 8217                        start: "{".to_string(),
 8218                        end: "}".to_string(),
 8219                        close: true,
 8220                        surround: true,
 8221                        newline: true,
 8222                    },
 8223                    BracketPair {
 8224                        start: "(".to_string(),
 8225                        end: ")".to_string(),
 8226                        close: true,
 8227                        surround: true,
 8228                        newline: true,
 8229                    },
 8230                    BracketPair {
 8231                        start: "/*".to_string(),
 8232                        end: " */".to_string(),
 8233                        close: true,
 8234                        surround: true,
 8235                        newline: true,
 8236                    },
 8237                    BracketPair {
 8238                        start: "[".to_string(),
 8239                        end: "]".to_string(),
 8240                        close: false,
 8241                        surround: false,
 8242                        newline: true,
 8243                    },
 8244                    BracketPair {
 8245                        start: "\"".to_string(),
 8246                        end: "\"".to_string(),
 8247                        close: true,
 8248                        surround: true,
 8249                        newline: false,
 8250                    },
 8251                    BracketPair {
 8252                        start: "<".to_string(),
 8253                        end: ">".to_string(),
 8254                        close: false,
 8255                        surround: true,
 8256                        newline: true,
 8257                    },
 8258                ],
 8259                ..Default::default()
 8260            },
 8261            autoclose_before: "})]".to_string(),
 8262            ..Default::default()
 8263        },
 8264        Some(tree_sitter_rust::LANGUAGE.into()),
 8265    ));
 8266
 8267    cx.language_registry().add(language.clone());
 8268    cx.update_buffer(|buffer, cx| {
 8269        buffer.set_language(Some(language), cx);
 8270    });
 8271
 8272    cx.set_state(
 8273        &r#"
 8274            🏀ˇ
 8275            εˇ
 8276            ❤️ˇ
 8277        "#
 8278        .unindent(),
 8279    );
 8280
 8281    // autoclose multiple nested brackets at multiple cursors
 8282    cx.update_editor(|editor, window, cx| {
 8283        editor.handle_input("{", window, cx);
 8284        editor.handle_input("{", window, cx);
 8285        editor.handle_input("{", window, cx);
 8286    });
 8287    cx.assert_editor_state(
 8288        &"
 8289            🏀{{{ˇ}}}
 8290            ε{{{ˇ}}}
 8291            ❤️{{{ˇ}}}
 8292        "
 8293        .unindent(),
 8294    );
 8295
 8296    // insert a different closing bracket
 8297    cx.update_editor(|editor, window, cx| {
 8298        editor.handle_input(")", window, cx);
 8299    });
 8300    cx.assert_editor_state(
 8301        &"
 8302            🏀{{{)ˇ}}}
 8303            ε{{{)ˇ}}}
 8304            ❤️{{{)ˇ}}}
 8305        "
 8306        .unindent(),
 8307    );
 8308
 8309    // skip over the auto-closed brackets when typing a closing bracket
 8310    cx.update_editor(|editor, window, cx| {
 8311        editor.move_right(&MoveRight, window, cx);
 8312        editor.handle_input("}", window, cx);
 8313        editor.handle_input("}", window, cx);
 8314        editor.handle_input("}", window, cx);
 8315    });
 8316    cx.assert_editor_state(
 8317        &"
 8318            🏀{{{)}}}}ˇ
 8319            ε{{{)}}}}ˇ
 8320            ❤️{{{)}}}}ˇ
 8321        "
 8322        .unindent(),
 8323    );
 8324
 8325    // autoclose multi-character pairs
 8326    cx.set_state(
 8327        &"
 8328            ˇ
 8329            ˇ
 8330        "
 8331        .unindent(),
 8332    );
 8333    cx.update_editor(|editor, window, cx| {
 8334        editor.handle_input("/", window, cx);
 8335        editor.handle_input("*", window, cx);
 8336    });
 8337    cx.assert_editor_state(
 8338        &"
 8339            /*ˇ */
 8340            /*ˇ */
 8341        "
 8342        .unindent(),
 8343    );
 8344
 8345    // one cursor autocloses a multi-character pair, one cursor
 8346    // does not autoclose.
 8347    cx.set_state(
 8348        &"
 8349 8350            ˇ
 8351        "
 8352        .unindent(),
 8353    );
 8354    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8355    cx.assert_editor_state(
 8356        &"
 8357            /*ˇ */
 8358 8359        "
 8360        .unindent(),
 8361    );
 8362
 8363    // Don't autoclose if the next character isn't whitespace and isn't
 8364    // listed in the language's "autoclose_before" section.
 8365    cx.set_state("ˇa b");
 8366    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8367    cx.assert_editor_state("{ˇa b");
 8368
 8369    // Don't autoclose if `close` is false for the bracket pair
 8370    cx.set_state("ˇ");
 8371    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8372    cx.assert_editor_state("");
 8373
 8374    // Surround with brackets if text is selected
 8375    cx.set_state("«aˇ» b");
 8376    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8377    cx.assert_editor_state("{«aˇ»} b");
 8378
 8379    // Autoclose when not immediately after a word character
 8380    cx.set_state("a ˇ");
 8381    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8382    cx.assert_editor_state("a \"ˇ\"");
 8383
 8384    // Autoclose pair where the start and end characters are the same
 8385    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8386    cx.assert_editor_state("a \"\"ˇ");
 8387
 8388    // Don't autoclose when immediately after a word character
 8389    cx.set_state("");
 8390    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8391    cx.assert_editor_state("a\"ˇ");
 8392
 8393    // Do autoclose when after a non-word character
 8394    cx.set_state("");
 8395    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8396    cx.assert_editor_state("{\"ˇ\"");
 8397
 8398    // Non identical pairs autoclose regardless of preceding character
 8399    cx.set_state("");
 8400    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8401    cx.assert_editor_state("a{ˇ}");
 8402
 8403    // Don't autoclose pair if autoclose is disabled
 8404    cx.set_state("ˇ");
 8405    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8406    cx.assert_editor_state("");
 8407
 8408    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8409    cx.set_state("«aˇ» b");
 8410    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8411    cx.assert_editor_state("<«aˇ»> b");
 8412}
 8413
 8414#[gpui::test]
 8415async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8416    init_test(cx, |settings| {
 8417        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8418    });
 8419
 8420    let mut cx = EditorTestContext::new(cx).await;
 8421
 8422    let language = Arc::new(Language::new(
 8423        LanguageConfig {
 8424            brackets: BracketPairConfig {
 8425                pairs: vec![
 8426                    BracketPair {
 8427                        start: "{".to_string(),
 8428                        end: "}".to_string(),
 8429                        close: true,
 8430                        surround: true,
 8431                        newline: true,
 8432                    },
 8433                    BracketPair {
 8434                        start: "(".to_string(),
 8435                        end: ")".to_string(),
 8436                        close: true,
 8437                        surround: true,
 8438                        newline: true,
 8439                    },
 8440                    BracketPair {
 8441                        start: "[".to_string(),
 8442                        end: "]".to_string(),
 8443                        close: false,
 8444                        surround: false,
 8445                        newline: true,
 8446                    },
 8447                ],
 8448                ..Default::default()
 8449            },
 8450            autoclose_before: "})]".to_string(),
 8451            ..Default::default()
 8452        },
 8453        Some(tree_sitter_rust::LANGUAGE.into()),
 8454    ));
 8455
 8456    cx.language_registry().add(language.clone());
 8457    cx.update_buffer(|buffer, cx| {
 8458        buffer.set_language(Some(language), cx);
 8459    });
 8460
 8461    cx.set_state(
 8462        &"
 8463            ˇ
 8464            ˇ
 8465            ˇ
 8466        "
 8467        .unindent(),
 8468    );
 8469
 8470    // ensure only matching closing brackets are skipped over
 8471    cx.update_editor(|editor, window, cx| {
 8472        editor.handle_input("}", window, cx);
 8473        editor.move_left(&MoveLeft, window, cx);
 8474        editor.handle_input(")", window, cx);
 8475        editor.move_left(&MoveLeft, window, cx);
 8476    });
 8477    cx.assert_editor_state(
 8478        &"
 8479            ˇ)}
 8480            ˇ)}
 8481            ˇ)}
 8482        "
 8483        .unindent(),
 8484    );
 8485
 8486    // skip-over closing brackets at multiple cursors
 8487    cx.update_editor(|editor, window, cx| {
 8488        editor.handle_input(")", window, cx);
 8489        editor.handle_input("}", window, cx);
 8490    });
 8491    cx.assert_editor_state(
 8492        &"
 8493            )}ˇ
 8494            )}ˇ
 8495            )}ˇ
 8496        "
 8497        .unindent(),
 8498    );
 8499
 8500    // ignore non-close brackets
 8501    cx.update_editor(|editor, window, cx| {
 8502        editor.handle_input("]", window, cx);
 8503        editor.move_left(&MoveLeft, window, cx);
 8504        editor.handle_input("]", window, cx);
 8505    });
 8506    cx.assert_editor_state(
 8507        &"
 8508            )}]ˇ]
 8509            )}]ˇ]
 8510            )}]ˇ]
 8511        "
 8512        .unindent(),
 8513    );
 8514}
 8515
 8516#[gpui::test]
 8517async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8518    init_test(cx, |_| {});
 8519
 8520    let mut cx = EditorTestContext::new(cx).await;
 8521
 8522    let html_language = Arc::new(
 8523        Language::new(
 8524            LanguageConfig {
 8525                name: "HTML".into(),
 8526                brackets: BracketPairConfig {
 8527                    pairs: vec![
 8528                        BracketPair {
 8529                            start: "<".into(),
 8530                            end: ">".into(),
 8531                            close: true,
 8532                            ..Default::default()
 8533                        },
 8534                        BracketPair {
 8535                            start: "{".into(),
 8536                            end: "}".into(),
 8537                            close: true,
 8538                            ..Default::default()
 8539                        },
 8540                        BracketPair {
 8541                            start: "(".into(),
 8542                            end: ")".into(),
 8543                            close: true,
 8544                            ..Default::default()
 8545                        },
 8546                    ],
 8547                    ..Default::default()
 8548                },
 8549                autoclose_before: "})]>".into(),
 8550                ..Default::default()
 8551            },
 8552            Some(tree_sitter_html::LANGUAGE.into()),
 8553        )
 8554        .with_injection_query(
 8555            r#"
 8556            (script_element
 8557                (raw_text) @injection.content
 8558                (#set! injection.language "javascript"))
 8559            "#,
 8560        )
 8561        .unwrap(),
 8562    );
 8563
 8564    let javascript_language = Arc::new(Language::new(
 8565        LanguageConfig {
 8566            name: "JavaScript".into(),
 8567            brackets: BracketPairConfig {
 8568                pairs: vec![
 8569                    BracketPair {
 8570                        start: "/*".into(),
 8571                        end: " */".into(),
 8572                        close: true,
 8573                        ..Default::default()
 8574                    },
 8575                    BracketPair {
 8576                        start: "{".into(),
 8577                        end: "}".into(),
 8578                        close: true,
 8579                        ..Default::default()
 8580                    },
 8581                    BracketPair {
 8582                        start: "(".into(),
 8583                        end: ")".into(),
 8584                        close: true,
 8585                        ..Default::default()
 8586                    },
 8587                ],
 8588                ..Default::default()
 8589            },
 8590            autoclose_before: "})]>".into(),
 8591            ..Default::default()
 8592        },
 8593        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8594    ));
 8595
 8596    cx.language_registry().add(html_language.clone());
 8597    cx.language_registry().add(javascript_language.clone());
 8598
 8599    cx.update_buffer(|buffer, cx| {
 8600        buffer.set_language(Some(html_language), cx);
 8601    });
 8602
 8603    cx.set_state(
 8604        &r#"
 8605            <body>ˇ
 8606                <script>
 8607                    var x = 1;ˇ
 8608                </script>
 8609            </body>ˇ
 8610        "#
 8611        .unindent(),
 8612    );
 8613
 8614    // Precondition: different languages are active at different locations.
 8615    cx.update_editor(|editor, window, cx| {
 8616        let snapshot = editor.snapshot(window, cx);
 8617        let cursors = editor.selections.ranges::<usize>(cx);
 8618        let languages = cursors
 8619            .iter()
 8620            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8621            .collect::<Vec<_>>();
 8622        assert_eq!(
 8623            languages,
 8624            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8625        );
 8626    });
 8627
 8628    // Angle brackets autoclose in HTML, but not JavaScript.
 8629    cx.update_editor(|editor, window, cx| {
 8630        editor.handle_input("<", window, cx);
 8631        editor.handle_input("a", window, cx);
 8632    });
 8633    cx.assert_editor_state(
 8634        &r#"
 8635            <body><aˇ>
 8636                <script>
 8637                    var x = 1;<aˇ
 8638                </script>
 8639            </body><aˇ>
 8640        "#
 8641        .unindent(),
 8642    );
 8643
 8644    // Curly braces and parens autoclose in both HTML and JavaScript.
 8645    cx.update_editor(|editor, window, cx| {
 8646        editor.handle_input(" b=", window, cx);
 8647        editor.handle_input("{", window, cx);
 8648        editor.handle_input("c", window, cx);
 8649        editor.handle_input("(", window, cx);
 8650    });
 8651    cx.assert_editor_state(
 8652        &r#"
 8653            <body><a b={c(ˇ)}>
 8654                <script>
 8655                    var x = 1;<a b={c(ˇ)}
 8656                </script>
 8657            </body><a b={c(ˇ)}>
 8658        "#
 8659        .unindent(),
 8660    );
 8661
 8662    // Brackets that were already autoclosed are skipped.
 8663    cx.update_editor(|editor, window, cx| {
 8664        editor.handle_input(")", window, cx);
 8665        editor.handle_input("d", window, cx);
 8666        editor.handle_input("}", window, cx);
 8667    });
 8668    cx.assert_editor_state(
 8669        &r#"
 8670            <body><a b={c()d}ˇ>
 8671                <script>
 8672                    var x = 1;<a b={c()d}ˇ
 8673                </script>
 8674            </body><a b={c()d}ˇ>
 8675        "#
 8676        .unindent(),
 8677    );
 8678    cx.update_editor(|editor, window, cx| {
 8679        editor.handle_input(">", window, cx);
 8680    });
 8681    cx.assert_editor_state(
 8682        &r#"
 8683            <body><a b={c()d}>ˇ
 8684                <script>
 8685                    var x = 1;<a b={c()d}>ˇ
 8686                </script>
 8687            </body><a b={c()d}>ˇ
 8688        "#
 8689        .unindent(),
 8690    );
 8691
 8692    // Reset
 8693    cx.set_state(
 8694        &r#"
 8695            <body>ˇ
 8696                <script>
 8697                    var x = 1;ˇ
 8698                </script>
 8699            </body>ˇ
 8700        "#
 8701        .unindent(),
 8702    );
 8703
 8704    cx.update_editor(|editor, window, cx| {
 8705        editor.handle_input("<", window, cx);
 8706    });
 8707    cx.assert_editor_state(
 8708        &r#"
 8709            <body><ˇ>
 8710                <script>
 8711                    var x = 1;<ˇ
 8712                </script>
 8713            </body><ˇ>
 8714        "#
 8715        .unindent(),
 8716    );
 8717
 8718    // When backspacing, the closing angle brackets are removed.
 8719    cx.update_editor(|editor, window, cx| {
 8720        editor.backspace(&Backspace, window, cx);
 8721    });
 8722    cx.assert_editor_state(
 8723        &r#"
 8724            <body>ˇ
 8725                <script>
 8726                    var x = 1;ˇ
 8727                </script>
 8728            </body>ˇ
 8729        "#
 8730        .unindent(),
 8731    );
 8732
 8733    // Block comments autoclose in JavaScript, but not HTML.
 8734    cx.update_editor(|editor, window, cx| {
 8735        editor.handle_input("/", window, cx);
 8736        editor.handle_input("*", window, cx);
 8737    });
 8738    cx.assert_editor_state(
 8739        &r#"
 8740            <body>/*ˇ
 8741                <script>
 8742                    var x = 1;/*ˇ */
 8743                </script>
 8744            </body>/*ˇ
 8745        "#
 8746        .unindent(),
 8747    );
 8748}
 8749
 8750#[gpui::test]
 8751async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8752    init_test(cx, |_| {});
 8753
 8754    let mut cx = EditorTestContext::new(cx).await;
 8755
 8756    let rust_language = Arc::new(
 8757        Language::new(
 8758            LanguageConfig {
 8759                name: "Rust".into(),
 8760                brackets: serde_json::from_value(json!([
 8761                    { "start": "{", "end": "}", "close": true, "newline": true },
 8762                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8763                ]))
 8764                .unwrap(),
 8765                autoclose_before: "})]>".into(),
 8766                ..Default::default()
 8767            },
 8768            Some(tree_sitter_rust::LANGUAGE.into()),
 8769        )
 8770        .with_override_query("(string_literal) @string")
 8771        .unwrap(),
 8772    );
 8773
 8774    cx.language_registry().add(rust_language.clone());
 8775    cx.update_buffer(|buffer, cx| {
 8776        buffer.set_language(Some(rust_language), cx);
 8777    });
 8778
 8779    cx.set_state(
 8780        &r#"
 8781            let x = ˇ
 8782        "#
 8783        .unindent(),
 8784    );
 8785
 8786    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8787    cx.update_editor(|editor, window, cx| {
 8788        editor.handle_input("\"", window, cx);
 8789    });
 8790    cx.assert_editor_state(
 8791        &r#"
 8792            let x = "ˇ"
 8793        "#
 8794        .unindent(),
 8795    );
 8796
 8797    // Inserting another quotation mark. The cursor moves across the existing
 8798    // automatically-inserted quotation mark.
 8799    cx.update_editor(|editor, window, cx| {
 8800        editor.handle_input("\"", window, cx);
 8801    });
 8802    cx.assert_editor_state(
 8803        &r#"
 8804            let x = ""ˇ
 8805        "#
 8806        .unindent(),
 8807    );
 8808
 8809    // Reset
 8810    cx.set_state(
 8811        &r#"
 8812            let x = ˇ
 8813        "#
 8814        .unindent(),
 8815    );
 8816
 8817    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8818    cx.update_editor(|editor, window, cx| {
 8819        editor.handle_input("\"", window, cx);
 8820        editor.handle_input(" ", window, cx);
 8821        editor.move_left(&Default::default(), window, cx);
 8822        editor.handle_input("\\", window, cx);
 8823        editor.handle_input("\"", window, cx);
 8824    });
 8825    cx.assert_editor_state(
 8826        &r#"
 8827            let x = "\"ˇ "
 8828        "#
 8829        .unindent(),
 8830    );
 8831
 8832    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8833    // mark. Nothing is inserted.
 8834    cx.update_editor(|editor, window, cx| {
 8835        editor.move_right(&Default::default(), window, cx);
 8836        editor.handle_input("\"", window, cx);
 8837    });
 8838    cx.assert_editor_state(
 8839        &r#"
 8840            let x = "\" "ˇ
 8841        "#
 8842        .unindent(),
 8843    );
 8844}
 8845
 8846#[gpui::test]
 8847async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8848    init_test(cx, |_| {});
 8849
 8850    let language = Arc::new(Language::new(
 8851        LanguageConfig {
 8852            brackets: BracketPairConfig {
 8853                pairs: vec![
 8854                    BracketPair {
 8855                        start: "{".to_string(),
 8856                        end: "}".to_string(),
 8857                        close: true,
 8858                        surround: true,
 8859                        newline: true,
 8860                    },
 8861                    BracketPair {
 8862                        start: "/* ".to_string(),
 8863                        end: "*/".to_string(),
 8864                        close: true,
 8865                        surround: true,
 8866                        ..Default::default()
 8867                    },
 8868                ],
 8869                ..Default::default()
 8870            },
 8871            ..Default::default()
 8872        },
 8873        Some(tree_sitter_rust::LANGUAGE.into()),
 8874    ));
 8875
 8876    let text = r#"
 8877        a
 8878        b
 8879        c
 8880    "#
 8881    .unindent();
 8882
 8883    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8884    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8885    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8886    editor
 8887        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8888        .await;
 8889
 8890    editor.update_in(cx, |editor, window, cx| {
 8891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8892            s.select_display_ranges([
 8893                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8894                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8895                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8896            ])
 8897        });
 8898
 8899        editor.handle_input("{", window, cx);
 8900        editor.handle_input("{", window, cx);
 8901        editor.handle_input("{", window, cx);
 8902        assert_eq!(
 8903            editor.text(cx),
 8904            "
 8905                {{{a}}}
 8906                {{{b}}}
 8907                {{{c}}}
 8908            "
 8909            .unindent()
 8910        );
 8911        assert_eq!(
 8912            editor.selections.display_ranges(cx),
 8913            [
 8914                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8915                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8916                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8917            ]
 8918        );
 8919
 8920        editor.undo(&Undo, window, cx);
 8921        editor.undo(&Undo, window, cx);
 8922        editor.undo(&Undo, window, cx);
 8923        assert_eq!(
 8924            editor.text(cx),
 8925            "
 8926                a
 8927                b
 8928                c
 8929            "
 8930            .unindent()
 8931        );
 8932        assert_eq!(
 8933            editor.selections.display_ranges(cx),
 8934            [
 8935                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8936                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8937                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8938            ]
 8939        );
 8940
 8941        // Ensure inserting the first character of a multi-byte bracket pair
 8942        // doesn't surround the selections with the bracket.
 8943        editor.handle_input("/", window, cx);
 8944        assert_eq!(
 8945            editor.text(cx),
 8946            "
 8947                /
 8948                /
 8949                /
 8950            "
 8951            .unindent()
 8952        );
 8953        assert_eq!(
 8954            editor.selections.display_ranges(cx),
 8955            [
 8956                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8957                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8958                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8959            ]
 8960        );
 8961
 8962        editor.undo(&Undo, window, cx);
 8963        assert_eq!(
 8964            editor.text(cx),
 8965            "
 8966                a
 8967                b
 8968                c
 8969            "
 8970            .unindent()
 8971        );
 8972        assert_eq!(
 8973            editor.selections.display_ranges(cx),
 8974            [
 8975                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8976                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8977                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8978            ]
 8979        );
 8980
 8981        // Ensure inserting the last character of a multi-byte bracket pair
 8982        // doesn't surround the selections with the bracket.
 8983        editor.handle_input("*", window, cx);
 8984        assert_eq!(
 8985            editor.text(cx),
 8986            "
 8987                *
 8988                *
 8989                *
 8990            "
 8991            .unindent()
 8992        );
 8993        assert_eq!(
 8994            editor.selections.display_ranges(cx),
 8995            [
 8996                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8997                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8998                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8999            ]
 9000        );
 9001    });
 9002}
 9003
 9004#[gpui::test]
 9005async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9006    init_test(cx, |_| {});
 9007
 9008    let language = Arc::new(Language::new(
 9009        LanguageConfig {
 9010            brackets: BracketPairConfig {
 9011                pairs: vec![BracketPair {
 9012                    start: "{".to_string(),
 9013                    end: "}".to_string(),
 9014                    close: true,
 9015                    surround: true,
 9016                    newline: true,
 9017                }],
 9018                ..Default::default()
 9019            },
 9020            autoclose_before: "}".to_string(),
 9021            ..Default::default()
 9022        },
 9023        Some(tree_sitter_rust::LANGUAGE.into()),
 9024    ));
 9025
 9026    let text = r#"
 9027        a
 9028        b
 9029        c
 9030    "#
 9031    .unindent();
 9032
 9033    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9034    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9035    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9036    editor
 9037        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9038        .await;
 9039
 9040    editor.update_in(cx, |editor, window, cx| {
 9041        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9042            s.select_ranges([
 9043                Point::new(0, 1)..Point::new(0, 1),
 9044                Point::new(1, 1)..Point::new(1, 1),
 9045                Point::new(2, 1)..Point::new(2, 1),
 9046            ])
 9047        });
 9048
 9049        editor.handle_input("{", window, cx);
 9050        editor.handle_input("{", window, cx);
 9051        editor.handle_input("_", window, cx);
 9052        assert_eq!(
 9053            editor.text(cx),
 9054            "
 9055                a{{_}}
 9056                b{{_}}
 9057                c{{_}}
 9058            "
 9059            .unindent()
 9060        );
 9061        assert_eq!(
 9062            editor.selections.ranges::<Point>(cx),
 9063            [
 9064                Point::new(0, 4)..Point::new(0, 4),
 9065                Point::new(1, 4)..Point::new(1, 4),
 9066                Point::new(2, 4)..Point::new(2, 4)
 9067            ]
 9068        );
 9069
 9070        editor.backspace(&Default::default(), window, cx);
 9071        editor.backspace(&Default::default(), window, cx);
 9072        assert_eq!(
 9073            editor.text(cx),
 9074            "
 9075                a{}
 9076                b{}
 9077                c{}
 9078            "
 9079            .unindent()
 9080        );
 9081        assert_eq!(
 9082            editor.selections.ranges::<Point>(cx),
 9083            [
 9084                Point::new(0, 2)..Point::new(0, 2),
 9085                Point::new(1, 2)..Point::new(1, 2),
 9086                Point::new(2, 2)..Point::new(2, 2)
 9087            ]
 9088        );
 9089
 9090        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9091        assert_eq!(
 9092            editor.text(cx),
 9093            "
 9094                a
 9095                b
 9096                c
 9097            "
 9098            .unindent()
 9099        );
 9100        assert_eq!(
 9101            editor.selections.ranges::<Point>(cx),
 9102            [
 9103                Point::new(0, 1)..Point::new(0, 1),
 9104                Point::new(1, 1)..Point::new(1, 1),
 9105                Point::new(2, 1)..Point::new(2, 1)
 9106            ]
 9107        );
 9108    });
 9109}
 9110
 9111#[gpui::test]
 9112async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9113    init_test(cx, |settings| {
 9114        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9115    });
 9116
 9117    let mut cx = EditorTestContext::new(cx).await;
 9118
 9119    let language = Arc::new(Language::new(
 9120        LanguageConfig {
 9121            brackets: BracketPairConfig {
 9122                pairs: vec![
 9123                    BracketPair {
 9124                        start: "{".to_string(),
 9125                        end: "}".to_string(),
 9126                        close: true,
 9127                        surround: true,
 9128                        newline: true,
 9129                    },
 9130                    BracketPair {
 9131                        start: "(".to_string(),
 9132                        end: ")".to_string(),
 9133                        close: true,
 9134                        surround: true,
 9135                        newline: true,
 9136                    },
 9137                    BracketPair {
 9138                        start: "[".to_string(),
 9139                        end: "]".to_string(),
 9140                        close: false,
 9141                        surround: true,
 9142                        newline: true,
 9143                    },
 9144                ],
 9145                ..Default::default()
 9146            },
 9147            autoclose_before: "})]".to_string(),
 9148            ..Default::default()
 9149        },
 9150        Some(tree_sitter_rust::LANGUAGE.into()),
 9151    ));
 9152
 9153    cx.language_registry().add(language.clone());
 9154    cx.update_buffer(|buffer, cx| {
 9155        buffer.set_language(Some(language), cx);
 9156    });
 9157
 9158    cx.set_state(
 9159        &"
 9160            {(ˇ)}
 9161            [[ˇ]]
 9162            {(ˇ)}
 9163        "
 9164        .unindent(),
 9165    );
 9166
 9167    cx.update_editor(|editor, window, cx| {
 9168        editor.backspace(&Default::default(), window, cx);
 9169        editor.backspace(&Default::default(), window, cx);
 9170    });
 9171
 9172    cx.assert_editor_state(
 9173        &"
 9174            ˇ
 9175            ˇ]]
 9176            ˇ
 9177        "
 9178        .unindent(),
 9179    );
 9180
 9181    cx.update_editor(|editor, window, cx| {
 9182        editor.handle_input("{", window, cx);
 9183        editor.handle_input("{", window, cx);
 9184        editor.move_right(&MoveRight, window, cx);
 9185        editor.move_right(&MoveRight, window, cx);
 9186        editor.move_left(&MoveLeft, window, cx);
 9187        editor.move_left(&MoveLeft, window, cx);
 9188        editor.backspace(&Default::default(), window, cx);
 9189    });
 9190
 9191    cx.assert_editor_state(
 9192        &"
 9193            {ˇ}
 9194            {ˇ}]]
 9195            {ˇ}
 9196        "
 9197        .unindent(),
 9198    );
 9199
 9200    cx.update_editor(|editor, window, cx| {
 9201        editor.backspace(&Default::default(), window, cx);
 9202    });
 9203
 9204    cx.assert_editor_state(
 9205        &"
 9206            ˇ
 9207            ˇ]]
 9208            ˇ
 9209        "
 9210        .unindent(),
 9211    );
 9212}
 9213
 9214#[gpui::test]
 9215async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9216    init_test(cx, |_| {});
 9217
 9218    let language = Arc::new(Language::new(
 9219        LanguageConfig::default(),
 9220        Some(tree_sitter_rust::LANGUAGE.into()),
 9221    ));
 9222
 9223    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9224    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9225    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9226    editor
 9227        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9228        .await;
 9229
 9230    editor.update_in(cx, |editor, window, cx| {
 9231        editor.set_auto_replace_emoji_shortcode(true);
 9232
 9233        editor.handle_input("Hello ", window, cx);
 9234        editor.handle_input(":wave", window, cx);
 9235        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9236
 9237        editor.handle_input(":", window, cx);
 9238        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9239
 9240        editor.handle_input(" :smile", window, cx);
 9241        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9242
 9243        editor.handle_input(":", window, cx);
 9244        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9245
 9246        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9247        editor.handle_input(":wave", window, cx);
 9248        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9249
 9250        editor.handle_input(":", window, cx);
 9251        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9252
 9253        editor.handle_input(":1", window, cx);
 9254        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9255
 9256        editor.handle_input(":", window, cx);
 9257        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9258
 9259        // Ensure shortcode does not get replaced when it is part of a word
 9260        editor.handle_input(" Test:wave", window, cx);
 9261        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9262
 9263        editor.handle_input(":", window, cx);
 9264        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9265
 9266        editor.set_auto_replace_emoji_shortcode(false);
 9267
 9268        // Ensure shortcode does not get replaced when auto replace is off
 9269        editor.handle_input(" :wave", window, cx);
 9270        assert_eq!(
 9271            editor.text(cx),
 9272            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9273        );
 9274
 9275        editor.handle_input(":", window, cx);
 9276        assert_eq!(
 9277            editor.text(cx),
 9278            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9279        );
 9280    });
 9281}
 9282
 9283#[gpui::test]
 9284async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9285    init_test(cx, |_| {});
 9286
 9287    let (text, insertion_ranges) = marked_text_ranges(
 9288        indoc! {"
 9289            ˇ
 9290        "},
 9291        false,
 9292    );
 9293
 9294    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9295    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9296
 9297    _ = editor.update_in(cx, |editor, window, cx| {
 9298        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9299
 9300        editor
 9301            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9302            .unwrap();
 9303
 9304        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9305            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9306            assert_eq!(editor.text(cx), expected_text);
 9307            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9308        }
 9309
 9310        assert(
 9311            editor,
 9312            cx,
 9313            indoc! {"
 9314            type «» =•
 9315            "},
 9316        );
 9317
 9318        assert!(editor.context_menu_visible(), "There should be a matches");
 9319    });
 9320}
 9321
 9322#[gpui::test]
 9323async fn test_snippets(cx: &mut TestAppContext) {
 9324    init_test(cx, |_| {});
 9325
 9326    let mut cx = EditorTestContext::new(cx).await;
 9327
 9328    cx.set_state(indoc! {"
 9329        a.ˇ b
 9330        a.ˇ b
 9331        a.ˇ b
 9332    "});
 9333
 9334    cx.update_editor(|editor, window, cx| {
 9335        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9336        let insertion_ranges = editor
 9337            .selections
 9338            .all(cx)
 9339            .iter()
 9340            .map(|s| s.range().clone())
 9341            .collect::<Vec<_>>();
 9342        editor
 9343            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9344            .unwrap();
 9345    });
 9346
 9347    cx.assert_editor_state(indoc! {"
 9348        a.f(«oneˇ», two, «threeˇ») b
 9349        a.f(«oneˇ», two, «threeˇ») b
 9350        a.f(«oneˇ», two, «threeˇ») b
 9351    "});
 9352
 9353    // Can't move earlier than the first tab stop
 9354    cx.update_editor(|editor, window, cx| {
 9355        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9356    });
 9357    cx.assert_editor_state(indoc! {"
 9358        a.f(«oneˇ», two, «threeˇ») b
 9359        a.f(«oneˇ», two, «threeˇ») b
 9360        a.f(«oneˇ», two, «threeˇ») b
 9361    "});
 9362
 9363    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 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    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9371    cx.assert_editor_state(indoc! {"
 9372        a.f(«oneˇ», two, «threeˇ») b
 9373        a.f(«oneˇ», two, «threeˇ») b
 9374        a.f(«oneˇ», two, «threeˇ») b
 9375    "});
 9376
 9377    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9378    cx.assert_editor_state(indoc! {"
 9379        a.f(one, «twoˇ», three) b
 9380        a.f(one, «twoˇ», three) b
 9381        a.f(one, «twoˇ», three) b
 9382    "});
 9383    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9384    cx.assert_editor_state(indoc! {"
 9385        a.f(one, two, three)ˇ b
 9386        a.f(one, two, three)ˇ b
 9387        a.f(one, two, three)ˇ b
 9388    "});
 9389
 9390    // As soon as the last tab stop is reached, snippet state is gone
 9391    cx.update_editor(|editor, window, cx| {
 9392        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9393    });
 9394    cx.assert_editor_state(indoc! {"
 9395        a.f(one, two, three)ˇ b
 9396        a.f(one, two, three)ˇ b
 9397        a.f(one, two, three)ˇ b
 9398    "});
 9399}
 9400
 9401#[gpui::test]
 9402async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9403    init_test(cx, |_| {});
 9404
 9405    let mut cx = EditorTestContext::new(cx).await;
 9406
 9407    cx.update_editor(|editor, window, cx| {
 9408        let snippet = Snippet::parse(indoc! {"
 9409            /*
 9410             * Multiline comment with leading indentation
 9411             *
 9412             * $1
 9413             */
 9414            $0"})
 9415        .unwrap();
 9416        let insertion_ranges = editor
 9417            .selections
 9418            .all(cx)
 9419            .iter()
 9420            .map(|s| s.range().clone())
 9421            .collect::<Vec<_>>();
 9422        editor
 9423            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9424            .unwrap();
 9425    });
 9426
 9427    cx.assert_editor_state(indoc! {"
 9428        /*
 9429         * Multiline comment with leading indentation
 9430         *
 9431         * ˇ
 9432         */
 9433    "});
 9434
 9435    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9436    cx.assert_editor_state(indoc! {"
 9437        /*
 9438         * Multiline comment with leading indentation
 9439         *
 9440         *•
 9441         */
 9442        ˇ"});
 9443}
 9444
 9445#[gpui::test]
 9446async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9447    init_test(cx, |_| {});
 9448
 9449    let fs = FakeFs::new(cx.executor());
 9450    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9451
 9452    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9453
 9454    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9455    language_registry.add(rust_lang());
 9456    let mut fake_servers = language_registry.register_fake_lsp(
 9457        "Rust",
 9458        FakeLspAdapter {
 9459            capabilities: lsp::ServerCapabilities {
 9460                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9461                ..Default::default()
 9462            },
 9463            ..Default::default()
 9464        },
 9465    );
 9466
 9467    let buffer = project
 9468        .update(cx, |project, cx| {
 9469            project.open_local_buffer(path!("/file.rs"), cx)
 9470        })
 9471        .await
 9472        .unwrap();
 9473
 9474    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9475    let (editor, cx) = cx.add_window_view(|window, cx| {
 9476        build_editor_with_project(project.clone(), buffer, window, cx)
 9477    });
 9478    editor.update_in(cx, |editor, window, cx| {
 9479        editor.set_text("one\ntwo\nthree\n", window, cx)
 9480    });
 9481    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9482
 9483    cx.executor().start_waiting();
 9484    let fake_server = fake_servers.next().await.unwrap();
 9485
 9486    {
 9487        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9488            move |params, _| async move {
 9489                assert_eq!(
 9490                    params.text_document.uri,
 9491                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9492                );
 9493                assert_eq!(params.options.tab_size, 4);
 9494                Ok(Some(vec![lsp::TextEdit::new(
 9495                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9496                    ", ".to_string(),
 9497                )]))
 9498            },
 9499        );
 9500        let save = editor
 9501            .update_in(cx, |editor, window, cx| {
 9502                editor.save(
 9503                    SaveOptions {
 9504                        format: true,
 9505                        autosave: false,
 9506                    },
 9507                    project.clone(),
 9508                    window,
 9509                    cx,
 9510                )
 9511            })
 9512            .unwrap();
 9513        cx.executor().start_waiting();
 9514        save.await;
 9515
 9516        assert_eq!(
 9517            editor.update(cx, |editor, cx| editor.text(cx)),
 9518            "one, two\nthree\n"
 9519        );
 9520        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9521    }
 9522
 9523    {
 9524        editor.update_in(cx, |editor, window, cx| {
 9525            editor.set_text("one\ntwo\nthree\n", window, cx)
 9526        });
 9527        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9528
 9529        // Ensure we can still save even if formatting hangs.
 9530        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9531            move |params, _| async move {
 9532                assert_eq!(
 9533                    params.text_document.uri,
 9534                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9535                );
 9536                futures::future::pending::<()>().await;
 9537                unreachable!()
 9538            },
 9539        );
 9540        let save = editor
 9541            .update_in(cx, |editor, window, cx| {
 9542                editor.save(
 9543                    SaveOptions {
 9544                        format: true,
 9545                        autosave: false,
 9546                    },
 9547                    project.clone(),
 9548                    window,
 9549                    cx,
 9550                )
 9551            })
 9552            .unwrap();
 9553        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9554        cx.executor().start_waiting();
 9555        save.await;
 9556        assert_eq!(
 9557            editor.update(cx, |editor, cx| editor.text(cx)),
 9558            "one\ntwo\nthree\n"
 9559        );
 9560    }
 9561
 9562    // Set rust language override and assert overridden tabsize is sent to language server
 9563    update_test_language_settings(cx, |settings| {
 9564        settings.languages.0.insert(
 9565            "Rust".into(),
 9566            LanguageSettingsContent {
 9567                tab_size: NonZeroU32::new(8),
 9568                ..Default::default()
 9569            },
 9570        );
 9571    });
 9572
 9573    {
 9574        editor.update_in(cx, |editor, window, cx| {
 9575            editor.set_text("somehting_new\n", window, cx)
 9576        });
 9577        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9578        let _formatting_request_signal = fake_server
 9579            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9580                assert_eq!(
 9581                    params.text_document.uri,
 9582                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9583                );
 9584                assert_eq!(params.options.tab_size, 8);
 9585                Ok(Some(vec![]))
 9586            });
 9587        let save = editor
 9588            .update_in(cx, |editor, window, cx| {
 9589                editor.save(
 9590                    SaveOptions {
 9591                        format: true,
 9592                        autosave: false,
 9593                    },
 9594                    project.clone(),
 9595                    window,
 9596                    cx,
 9597                )
 9598            })
 9599            .unwrap();
 9600        cx.executor().start_waiting();
 9601        save.await;
 9602    }
 9603}
 9604
 9605#[gpui::test]
 9606async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9607    init_test(cx, |settings| {
 9608        settings.defaults.ensure_final_newline_on_save = Some(false);
 9609    });
 9610
 9611    let fs = FakeFs::new(cx.executor());
 9612    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9613
 9614    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9615
 9616    let buffer = project
 9617        .update(cx, |project, cx| {
 9618            project.open_local_buffer(path!("/file.txt"), cx)
 9619        })
 9620        .await
 9621        .unwrap();
 9622
 9623    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9624    let (editor, cx) = cx.add_window_view(|window, cx| {
 9625        build_editor_with_project(project.clone(), buffer, window, cx)
 9626    });
 9627    editor.update_in(cx, |editor, window, cx| {
 9628        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9629            s.select_ranges([0..0])
 9630        });
 9631    });
 9632    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9633
 9634    editor.update_in(cx, |editor, window, cx| {
 9635        editor.handle_input("\n", window, cx)
 9636    });
 9637    cx.run_until_parked();
 9638    save(&editor, &project, cx).await;
 9639    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9640
 9641    editor.update_in(cx, |editor, window, cx| {
 9642        editor.undo(&Default::default(), window, cx);
 9643    });
 9644    save(&editor, &project, cx).await;
 9645    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9646
 9647    editor.update_in(cx, |editor, window, cx| {
 9648        editor.redo(&Default::default(), window, cx);
 9649    });
 9650    cx.run_until_parked();
 9651    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9652
 9653    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9654        let save = editor
 9655            .update_in(cx, |editor, window, cx| {
 9656                editor.save(
 9657                    SaveOptions {
 9658                        format: true,
 9659                        autosave: false,
 9660                    },
 9661                    project.clone(),
 9662                    window,
 9663                    cx,
 9664                )
 9665            })
 9666            .unwrap();
 9667        cx.executor().start_waiting();
 9668        save.await;
 9669        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9670    }
 9671}
 9672
 9673#[gpui::test]
 9674async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9675    init_test(cx, |_| {});
 9676
 9677    let cols = 4;
 9678    let rows = 10;
 9679    let sample_text_1 = sample_text(rows, cols, 'a');
 9680    assert_eq!(
 9681        sample_text_1,
 9682        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9683    );
 9684    let sample_text_2 = sample_text(rows, cols, 'l');
 9685    assert_eq!(
 9686        sample_text_2,
 9687        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9688    );
 9689    let sample_text_3 = sample_text(rows, cols, 'v');
 9690    assert_eq!(
 9691        sample_text_3,
 9692        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9693    );
 9694
 9695    let fs = FakeFs::new(cx.executor());
 9696    fs.insert_tree(
 9697        path!("/a"),
 9698        json!({
 9699            "main.rs": sample_text_1,
 9700            "other.rs": sample_text_2,
 9701            "lib.rs": sample_text_3,
 9702        }),
 9703    )
 9704    .await;
 9705
 9706    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9707    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9708    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9709
 9710    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9711    language_registry.add(rust_lang());
 9712    let mut fake_servers = language_registry.register_fake_lsp(
 9713        "Rust",
 9714        FakeLspAdapter {
 9715            capabilities: lsp::ServerCapabilities {
 9716                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9717                ..Default::default()
 9718            },
 9719            ..Default::default()
 9720        },
 9721    );
 9722
 9723    let worktree = project.update(cx, |project, cx| {
 9724        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9725        assert_eq!(worktrees.len(), 1);
 9726        worktrees.pop().unwrap()
 9727    });
 9728    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9729
 9730    let buffer_1 = project
 9731        .update(cx, |project, cx| {
 9732            project.open_buffer((worktree_id, "main.rs"), cx)
 9733        })
 9734        .await
 9735        .unwrap();
 9736    let buffer_2 = project
 9737        .update(cx, |project, cx| {
 9738            project.open_buffer((worktree_id, "other.rs"), cx)
 9739        })
 9740        .await
 9741        .unwrap();
 9742    let buffer_3 = project
 9743        .update(cx, |project, cx| {
 9744            project.open_buffer((worktree_id, "lib.rs"), cx)
 9745        })
 9746        .await
 9747        .unwrap();
 9748
 9749    let multi_buffer = cx.new(|cx| {
 9750        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9751        multi_buffer.push_excerpts(
 9752            buffer_1.clone(),
 9753            [
 9754                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9755                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9756                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9757            ],
 9758            cx,
 9759        );
 9760        multi_buffer.push_excerpts(
 9761            buffer_2.clone(),
 9762            [
 9763                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9764                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9765                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9766            ],
 9767            cx,
 9768        );
 9769        multi_buffer.push_excerpts(
 9770            buffer_3.clone(),
 9771            [
 9772                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9773                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9774                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9775            ],
 9776            cx,
 9777        );
 9778        multi_buffer
 9779    });
 9780    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9781        Editor::new(
 9782            EditorMode::full(),
 9783            multi_buffer,
 9784            Some(project.clone()),
 9785            window,
 9786            cx,
 9787        )
 9788    });
 9789
 9790    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9791        editor.change_selections(
 9792            SelectionEffects::scroll(Autoscroll::Next),
 9793            window,
 9794            cx,
 9795            |s| s.select_ranges(Some(1..2)),
 9796        );
 9797        editor.insert("|one|two|three|", window, cx);
 9798    });
 9799    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9800    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9801        editor.change_selections(
 9802            SelectionEffects::scroll(Autoscroll::Next),
 9803            window,
 9804            cx,
 9805            |s| s.select_ranges(Some(60..70)),
 9806        );
 9807        editor.insert("|four|five|six|", window, cx);
 9808    });
 9809    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9810
 9811    // First two buffers should be edited, but not the third one.
 9812    assert_eq!(
 9813        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9814        "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}",
 9815    );
 9816    buffer_1.update(cx, |buffer, _| {
 9817        assert!(buffer.is_dirty());
 9818        assert_eq!(
 9819            buffer.text(),
 9820            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9821        )
 9822    });
 9823    buffer_2.update(cx, |buffer, _| {
 9824        assert!(buffer.is_dirty());
 9825        assert_eq!(
 9826            buffer.text(),
 9827            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9828        )
 9829    });
 9830    buffer_3.update(cx, |buffer, _| {
 9831        assert!(!buffer.is_dirty());
 9832        assert_eq!(buffer.text(), sample_text_3,)
 9833    });
 9834    cx.executor().run_until_parked();
 9835
 9836    cx.executor().start_waiting();
 9837    let save = multi_buffer_editor
 9838        .update_in(cx, |editor, window, cx| {
 9839            editor.save(
 9840                SaveOptions {
 9841                    format: true,
 9842                    autosave: false,
 9843                },
 9844                project.clone(),
 9845                window,
 9846                cx,
 9847            )
 9848        })
 9849        .unwrap();
 9850
 9851    let fake_server = fake_servers.next().await.unwrap();
 9852    fake_server
 9853        .server
 9854        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9855            Ok(Some(vec![lsp::TextEdit::new(
 9856                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9857                format!("[{} formatted]", params.text_document.uri),
 9858            )]))
 9859        })
 9860        .detach();
 9861    save.await;
 9862
 9863    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9864    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9865    assert_eq!(
 9866        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9867        uri!(
 9868            "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}"
 9869        ),
 9870    );
 9871    buffer_1.update(cx, |buffer, _| {
 9872        assert!(!buffer.is_dirty());
 9873        assert_eq!(
 9874            buffer.text(),
 9875            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9876        )
 9877    });
 9878    buffer_2.update(cx, |buffer, _| {
 9879        assert!(!buffer.is_dirty());
 9880        assert_eq!(
 9881            buffer.text(),
 9882            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9883        )
 9884    });
 9885    buffer_3.update(cx, |buffer, _| {
 9886        assert!(!buffer.is_dirty());
 9887        assert_eq!(buffer.text(), sample_text_3,)
 9888    });
 9889}
 9890
 9891#[gpui::test]
 9892async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9893    init_test(cx, |_| {});
 9894
 9895    let fs = FakeFs::new(cx.executor());
 9896    fs.insert_tree(
 9897        path!("/dir"),
 9898        json!({
 9899            "file1.rs": "fn main() { println!(\"hello\"); }",
 9900            "file2.rs": "fn test() { println!(\"test\"); }",
 9901            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9902        }),
 9903    )
 9904    .await;
 9905
 9906    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9907    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9908    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9909
 9910    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9911    language_registry.add(rust_lang());
 9912
 9913    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9914    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9915
 9916    // Open three buffers
 9917    let buffer_1 = project
 9918        .update(cx, |project, cx| {
 9919            project.open_buffer((worktree_id, "file1.rs"), cx)
 9920        })
 9921        .await
 9922        .unwrap();
 9923    let buffer_2 = project
 9924        .update(cx, |project, cx| {
 9925            project.open_buffer((worktree_id, "file2.rs"), cx)
 9926        })
 9927        .await
 9928        .unwrap();
 9929    let buffer_3 = project
 9930        .update(cx, |project, cx| {
 9931            project.open_buffer((worktree_id, "file3.rs"), cx)
 9932        })
 9933        .await
 9934        .unwrap();
 9935
 9936    // Create a multi-buffer with all three buffers
 9937    let multi_buffer = cx.new(|cx| {
 9938        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9939        multi_buffer.push_excerpts(
 9940            buffer_1.clone(),
 9941            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9942            cx,
 9943        );
 9944        multi_buffer.push_excerpts(
 9945            buffer_2.clone(),
 9946            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9947            cx,
 9948        );
 9949        multi_buffer.push_excerpts(
 9950            buffer_3.clone(),
 9951            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9952            cx,
 9953        );
 9954        multi_buffer
 9955    });
 9956
 9957    let editor = cx.new_window_entity(|window, cx| {
 9958        Editor::new(
 9959            EditorMode::full(),
 9960            multi_buffer,
 9961            Some(project.clone()),
 9962            window,
 9963            cx,
 9964        )
 9965    });
 9966
 9967    // Edit only the first buffer
 9968    editor.update_in(cx, |editor, window, cx| {
 9969        editor.change_selections(
 9970            SelectionEffects::scroll(Autoscroll::Next),
 9971            window,
 9972            cx,
 9973            |s| s.select_ranges(Some(10..10)),
 9974        );
 9975        editor.insert("// edited", window, cx);
 9976    });
 9977
 9978    // Verify that only buffer 1 is dirty
 9979    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9980    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9981    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9982
 9983    // Get write counts after file creation (files were created with initial content)
 9984    // We expect each file to have been written once during creation
 9985    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9986    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9987    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9988
 9989    // Perform autosave
 9990    let save_task = editor.update_in(cx, |editor, window, cx| {
 9991        editor.save(
 9992            SaveOptions {
 9993                format: true,
 9994                autosave: true,
 9995            },
 9996            project.clone(),
 9997            window,
 9998            cx,
 9999        )
10000    });
10001    save_task.await.unwrap();
10002
10003    // Only the dirty buffer should have been saved
10004    assert_eq!(
10005        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10006        1,
10007        "Buffer 1 was dirty, so it should have been written once during autosave"
10008    );
10009    assert_eq!(
10010        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10011        0,
10012        "Buffer 2 was clean, so it should not have been written during autosave"
10013    );
10014    assert_eq!(
10015        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10016        0,
10017        "Buffer 3 was clean, so it should not have been written during autosave"
10018    );
10019
10020    // Verify buffer states after autosave
10021    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10022    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10023    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10024
10025    // Now perform a manual save (format = true)
10026    let save_task = editor.update_in(cx, |editor, window, cx| {
10027        editor.save(
10028            SaveOptions {
10029                format: true,
10030                autosave: false,
10031            },
10032            project.clone(),
10033            window,
10034            cx,
10035        )
10036    });
10037    save_task.await.unwrap();
10038
10039    // During manual save, clean buffers don't get written to disk
10040    // They just get did_save called for language server notifications
10041    assert_eq!(
10042        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10043        1,
10044        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10045    );
10046    assert_eq!(
10047        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10048        0,
10049        "Buffer 2 should not have been written at all"
10050    );
10051    assert_eq!(
10052        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10053        0,
10054        "Buffer 3 should not have been written at all"
10055    );
10056}
10057
10058#[gpui::test]
10059async fn test_range_format_during_save(cx: &mut TestAppContext) {
10060    init_test(cx, |_| {});
10061
10062    let fs = FakeFs::new(cx.executor());
10063    fs.insert_file(path!("/file.rs"), Default::default()).await;
10064
10065    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10066
10067    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10068    language_registry.add(rust_lang());
10069    let mut fake_servers = language_registry.register_fake_lsp(
10070        "Rust",
10071        FakeLspAdapter {
10072            capabilities: lsp::ServerCapabilities {
10073                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10074                ..Default::default()
10075            },
10076            ..Default::default()
10077        },
10078    );
10079
10080    let buffer = project
10081        .update(cx, |project, cx| {
10082            project.open_local_buffer(path!("/file.rs"), cx)
10083        })
10084        .await
10085        .unwrap();
10086
10087    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10088    let (editor, cx) = cx.add_window_view(|window, cx| {
10089        build_editor_with_project(project.clone(), buffer, window, cx)
10090    });
10091    editor.update_in(cx, |editor, window, cx| {
10092        editor.set_text("one\ntwo\nthree\n", window, cx)
10093    });
10094    assert!(cx.read(|cx| editor.is_dirty(cx)));
10095
10096    cx.executor().start_waiting();
10097    let fake_server = fake_servers.next().await.unwrap();
10098
10099    let save = editor
10100        .update_in(cx, |editor, window, cx| {
10101            editor.save(
10102                SaveOptions {
10103                    format: true,
10104                    autosave: false,
10105                },
10106                project.clone(),
10107                window,
10108                cx,
10109            )
10110        })
10111        .unwrap();
10112    fake_server
10113        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10114            assert_eq!(
10115                params.text_document.uri,
10116                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10117            );
10118            assert_eq!(params.options.tab_size, 4);
10119            Ok(Some(vec![lsp::TextEdit::new(
10120                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10121                ", ".to_string(),
10122            )]))
10123        })
10124        .next()
10125        .await;
10126    cx.executor().start_waiting();
10127    save.await;
10128    assert_eq!(
10129        editor.update(cx, |editor, cx| editor.text(cx)),
10130        "one, two\nthree\n"
10131    );
10132    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10133
10134    editor.update_in(cx, |editor, window, cx| {
10135        editor.set_text("one\ntwo\nthree\n", window, cx)
10136    });
10137    assert!(cx.read(|cx| editor.is_dirty(cx)));
10138
10139    // Ensure we can still save even if formatting hangs.
10140    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10141        move |params, _| async move {
10142            assert_eq!(
10143                params.text_document.uri,
10144                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10145            );
10146            futures::future::pending::<()>().await;
10147            unreachable!()
10148        },
10149    );
10150    let save = editor
10151        .update_in(cx, |editor, window, cx| {
10152            editor.save(
10153                SaveOptions {
10154                    format: true,
10155                    autosave: false,
10156                },
10157                project.clone(),
10158                window,
10159                cx,
10160            )
10161        })
10162        .unwrap();
10163    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10164    cx.executor().start_waiting();
10165    save.await;
10166    assert_eq!(
10167        editor.update(cx, |editor, cx| editor.text(cx)),
10168        "one\ntwo\nthree\n"
10169    );
10170    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10171
10172    // For non-dirty buffer, no formatting request should be sent
10173    let save = editor
10174        .update_in(cx, |editor, window, cx| {
10175            editor.save(
10176                SaveOptions {
10177                    format: false,
10178                    autosave: false,
10179                },
10180                project.clone(),
10181                window,
10182                cx,
10183            )
10184        })
10185        .unwrap();
10186    let _pending_format_request = fake_server
10187        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10188            panic!("Should not be invoked");
10189        })
10190        .next();
10191    cx.executor().start_waiting();
10192    save.await;
10193
10194    // Set Rust language override and assert overridden tabsize is sent to language server
10195    update_test_language_settings(cx, |settings| {
10196        settings.languages.0.insert(
10197            "Rust".into(),
10198            LanguageSettingsContent {
10199                tab_size: NonZeroU32::new(8),
10200                ..Default::default()
10201            },
10202        );
10203    });
10204
10205    editor.update_in(cx, |editor, window, cx| {
10206        editor.set_text("somehting_new\n", window, cx)
10207    });
10208    assert!(cx.read(|cx| editor.is_dirty(cx)));
10209    let save = editor
10210        .update_in(cx, |editor, window, cx| {
10211            editor.save(
10212                SaveOptions {
10213                    format: true,
10214                    autosave: false,
10215                },
10216                project.clone(),
10217                window,
10218                cx,
10219            )
10220        })
10221        .unwrap();
10222    fake_server
10223        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10224            assert_eq!(
10225                params.text_document.uri,
10226                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10227            );
10228            assert_eq!(params.options.tab_size, 8);
10229            Ok(Some(Vec::new()))
10230        })
10231        .next()
10232        .await;
10233    save.await;
10234}
10235
10236#[gpui::test]
10237async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10238    init_test(cx, |settings| {
10239        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10240            Formatter::LanguageServer { name: None },
10241        )))
10242    });
10243
10244    let fs = FakeFs::new(cx.executor());
10245    fs.insert_file(path!("/file.rs"), Default::default()).await;
10246
10247    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10248
10249    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10250    language_registry.add(Arc::new(Language::new(
10251        LanguageConfig {
10252            name: "Rust".into(),
10253            matcher: LanguageMatcher {
10254                path_suffixes: vec!["rs".to_string()],
10255                ..Default::default()
10256            },
10257            ..LanguageConfig::default()
10258        },
10259        Some(tree_sitter_rust::LANGUAGE.into()),
10260    )));
10261    update_test_language_settings(cx, |settings| {
10262        // Enable Prettier formatting for the same buffer, and ensure
10263        // LSP is called instead of Prettier.
10264        settings.defaults.prettier = Some(PrettierSettings {
10265            allowed: true,
10266            ..PrettierSettings::default()
10267        });
10268    });
10269    let mut fake_servers = language_registry.register_fake_lsp(
10270        "Rust",
10271        FakeLspAdapter {
10272            capabilities: lsp::ServerCapabilities {
10273                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10274                ..Default::default()
10275            },
10276            ..Default::default()
10277        },
10278    );
10279
10280    let buffer = project
10281        .update(cx, |project, cx| {
10282            project.open_local_buffer(path!("/file.rs"), cx)
10283        })
10284        .await
10285        .unwrap();
10286
10287    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10288    let (editor, cx) = cx.add_window_view(|window, cx| {
10289        build_editor_with_project(project.clone(), buffer, window, cx)
10290    });
10291    editor.update_in(cx, |editor, window, cx| {
10292        editor.set_text("one\ntwo\nthree\n", window, cx)
10293    });
10294
10295    cx.executor().start_waiting();
10296    let fake_server = fake_servers.next().await.unwrap();
10297
10298    let format = editor
10299        .update_in(cx, |editor, window, cx| {
10300            editor.perform_format(
10301                project.clone(),
10302                FormatTrigger::Manual,
10303                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10304                window,
10305                cx,
10306            )
10307        })
10308        .unwrap();
10309    fake_server
10310        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10311            assert_eq!(
10312                params.text_document.uri,
10313                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10314            );
10315            assert_eq!(params.options.tab_size, 4);
10316            Ok(Some(vec![lsp::TextEdit::new(
10317                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10318                ", ".to_string(),
10319            )]))
10320        })
10321        .next()
10322        .await;
10323    cx.executor().start_waiting();
10324    format.await;
10325    assert_eq!(
10326        editor.update(cx, |editor, cx| editor.text(cx)),
10327        "one, two\nthree\n"
10328    );
10329
10330    editor.update_in(cx, |editor, window, cx| {
10331        editor.set_text("one\ntwo\nthree\n", window, cx)
10332    });
10333    // Ensure we don't lock if formatting hangs.
10334    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10335        move |params, _| async move {
10336            assert_eq!(
10337                params.text_document.uri,
10338                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10339            );
10340            futures::future::pending::<()>().await;
10341            unreachable!()
10342        },
10343    );
10344    let format = editor
10345        .update_in(cx, |editor, window, cx| {
10346            editor.perform_format(
10347                project,
10348                FormatTrigger::Manual,
10349                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10350                window,
10351                cx,
10352            )
10353        })
10354        .unwrap();
10355    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10356    cx.executor().start_waiting();
10357    format.await;
10358    assert_eq!(
10359        editor.update(cx, |editor, cx| editor.text(cx)),
10360        "one\ntwo\nthree\n"
10361    );
10362}
10363
10364#[gpui::test]
10365async fn test_multiple_formatters(cx: &mut TestAppContext) {
10366    init_test(cx, |settings| {
10367        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10368        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10369            Formatter::LanguageServer { name: None },
10370            Formatter::CodeActions(
10371                [
10372                    ("code-action-1".into(), true),
10373                    ("code-action-2".into(), true),
10374                ]
10375                .into_iter()
10376                .collect(),
10377            ),
10378        ])))
10379    });
10380
10381    let fs = FakeFs::new(cx.executor());
10382    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10383        .await;
10384
10385    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10386    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10387    language_registry.add(rust_lang());
10388
10389    let mut fake_servers = language_registry.register_fake_lsp(
10390        "Rust",
10391        FakeLspAdapter {
10392            capabilities: lsp::ServerCapabilities {
10393                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10394                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10395                    commands: vec!["the-command-for-code-action-1".into()],
10396                    ..Default::default()
10397                }),
10398                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10399                ..Default::default()
10400            },
10401            ..Default::default()
10402        },
10403    );
10404
10405    let buffer = project
10406        .update(cx, |project, cx| {
10407            project.open_local_buffer(path!("/file.rs"), cx)
10408        })
10409        .await
10410        .unwrap();
10411
10412    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10413    let (editor, cx) = cx.add_window_view(|window, cx| {
10414        build_editor_with_project(project.clone(), buffer, window, cx)
10415    });
10416
10417    cx.executor().start_waiting();
10418
10419    let fake_server = fake_servers.next().await.unwrap();
10420    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10421        move |_params, _| async move {
10422            Ok(Some(vec![lsp::TextEdit::new(
10423                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10424                "applied-formatting\n".to_string(),
10425            )]))
10426        },
10427    );
10428    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10429        move |params, _| async move {
10430            assert_eq!(
10431                params.context.only,
10432                Some(vec!["code-action-1".into(), "code-action-2".into()])
10433            );
10434            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10435            Ok(Some(vec![
10436                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10437                    kind: Some("code-action-1".into()),
10438                    edit: Some(lsp::WorkspaceEdit::new(
10439                        [(
10440                            uri.clone(),
10441                            vec![lsp::TextEdit::new(
10442                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10443                                "applied-code-action-1-edit\n".to_string(),
10444                            )],
10445                        )]
10446                        .into_iter()
10447                        .collect(),
10448                    )),
10449                    command: Some(lsp::Command {
10450                        command: "the-command-for-code-action-1".into(),
10451                        ..Default::default()
10452                    }),
10453                    ..Default::default()
10454                }),
10455                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10456                    kind: Some("code-action-2".into()),
10457                    edit: Some(lsp::WorkspaceEdit::new(
10458                        [(
10459                            uri.clone(),
10460                            vec![lsp::TextEdit::new(
10461                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10462                                "applied-code-action-2-edit\n".to_string(),
10463                            )],
10464                        )]
10465                        .into_iter()
10466                        .collect(),
10467                    )),
10468                    ..Default::default()
10469                }),
10470            ]))
10471        },
10472    );
10473
10474    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10475        move |params, _| async move { Ok(params) }
10476    });
10477
10478    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10479    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10480        let fake = fake_server.clone();
10481        let lock = command_lock.clone();
10482        move |params, _| {
10483            assert_eq!(params.command, "the-command-for-code-action-1");
10484            let fake = fake.clone();
10485            let lock = lock.clone();
10486            async move {
10487                lock.lock().await;
10488                fake.server
10489                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10490                        label: None,
10491                        edit: lsp::WorkspaceEdit {
10492                            changes: Some(
10493                                [(
10494                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10495                                    vec![lsp::TextEdit {
10496                                        range: lsp::Range::new(
10497                                            lsp::Position::new(0, 0),
10498                                            lsp::Position::new(0, 0),
10499                                        ),
10500                                        new_text: "applied-code-action-1-command\n".into(),
10501                                    }],
10502                                )]
10503                                .into_iter()
10504                                .collect(),
10505                            ),
10506                            ..Default::default()
10507                        },
10508                    })
10509                    .await
10510                    .into_response()
10511                    .unwrap();
10512                Ok(Some(json!(null)))
10513            }
10514        }
10515    });
10516
10517    cx.executor().start_waiting();
10518    editor
10519        .update_in(cx, |editor, window, cx| {
10520            editor.perform_format(
10521                project.clone(),
10522                FormatTrigger::Manual,
10523                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10524                window,
10525                cx,
10526            )
10527        })
10528        .unwrap()
10529        .await;
10530    editor.update(cx, |editor, cx| {
10531        assert_eq!(
10532            editor.text(cx),
10533            r#"
10534                applied-code-action-2-edit
10535                applied-code-action-1-command
10536                applied-code-action-1-edit
10537                applied-formatting
10538                one
10539                two
10540                three
10541            "#
10542            .unindent()
10543        );
10544    });
10545
10546    editor.update_in(cx, |editor, window, cx| {
10547        editor.undo(&Default::default(), window, cx);
10548        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10549    });
10550
10551    // Perform a manual edit while waiting for an LSP command
10552    // that's being run as part of a formatting code action.
10553    let lock_guard = command_lock.lock().await;
10554    let format = editor
10555        .update_in(cx, |editor, window, cx| {
10556            editor.perform_format(
10557                project.clone(),
10558                FormatTrigger::Manual,
10559                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10560                window,
10561                cx,
10562            )
10563        })
10564        .unwrap();
10565    cx.run_until_parked();
10566    editor.update(cx, |editor, cx| {
10567        assert_eq!(
10568            editor.text(cx),
10569            r#"
10570                applied-code-action-1-edit
10571                applied-formatting
10572                one
10573                two
10574                three
10575            "#
10576            .unindent()
10577        );
10578
10579        editor.buffer.update(cx, |buffer, cx| {
10580            let ix = buffer.len(cx);
10581            buffer.edit([(ix..ix, "edited\n")], None, cx);
10582        });
10583    });
10584
10585    // Allow the LSP command to proceed. Because the buffer was edited,
10586    // the second code action will not be run.
10587    drop(lock_guard);
10588    format.await;
10589    editor.update_in(cx, |editor, window, cx| {
10590        assert_eq!(
10591            editor.text(cx),
10592            r#"
10593                applied-code-action-1-command
10594                applied-code-action-1-edit
10595                applied-formatting
10596                one
10597                two
10598                three
10599                edited
10600            "#
10601            .unindent()
10602        );
10603
10604        // The manual edit is undone first, because it is the last thing the user did
10605        // (even though the command completed afterwards).
10606        editor.undo(&Default::default(), window, cx);
10607        assert_eq!(
10608            editor.text(cx),
10609            r#"
10610                applied-code-action-1-command
10611                applied-code-action-1-edit
10612                applied-formatting
10613                one
10614                two
10615                three
10616            "#
10617            .unindent()
10618        );
10619
10620        // All the formatting (including the command, which completed after the manual edit)
10621        // is undone together.
10622        editor.undo(&Default::default(), window, cx);
10623        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10624    });
10625}
10626
10627#[gpui::test]
10628async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10629    init_test(cx, |settings| {
10630        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10631            Formatter::LanguageServer { name: None },
10632        ])))
10633    });
10634
10635    let fs = FakeFs::new(cx.executor());
10636    fs.insert_file(path!("/file.ts"), Default::default()).await;
10637
10638    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10639
10640    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10641    language_registry.add(Arc::new(Language::new(
10642        LanguageConfig {
10643            name: "TypeScript".into(),
10644            matcher: LanguageMatcher {
10645                path_suffixes: vec!["ts".to_string()],
10646                ..Default::default()
10647            },
10648            ..LanguageConfig::default()
10649        },
10650        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10651    )));
10652    update_test_language_settings(cx, |settings| {
10653        settings.defaults.prettier = Some(PrettierSettings {
10654            allowed: true,
10655            ..PrettierSettings::default()
10656        });
10657    });
10658    let mut fake_servers = language_registry.register_fake_lsp(
10659        "TypeScript",
10660        FakeLspAdapter {
10661            capabilities: lsp::ServerCapabilities {
10662                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10663                ..Default::default()
10664            },
10665            ..Default::default()
10666        },
10667    );
10668
10669    let buffer = project
10670        .update(cx, |project, cx| {
10671            project.open_local_buffer(path!("/file.ts"), cx)
10672        })
10673        .await
10674        .unwrap();
10675
10676    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10677    let (editor, cx) = cx.add_window_view(|window, cx| {
10678        build_editor_with_project(project.clone(), buffer, window, cx)
10679    });
10680    editor.update_in(cx, |editor, window, cx| {
10681        editor.set_text(
10682            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10683            window,
10684            cx,
10685        )
10686    });
10687
10688    cx.executor().start_waiting();
10689    let fake_server = fake_servers.next().await.unwrap();
10690
10691    let format = editor
10692        .update_in(cx, |editor, window, cx| {
10693            editor.perform_code_action_kind(
10694                project.clone(),
10695                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10696                window,
10697                cx,
10698            )
10699        })
10700        .unwrap();
10701    fake_server
10702        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10703            assert_eq!(
10704                params.text_document.uri,
10705                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10706            );
10707            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10708                lsp::CodeAction {
10709                    title: "Organize Imports".to_string(),
10710                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10711                    edit: Some(lsp::WorkspaceEdit {
10712                        changes: Some(
10713                            [(
10714                                params.text_document.uri.clone(),
10715                                vec![lsp::TextEdit::new(
10716                                    lsp::Range::new(
10717                                        lsp::Position::new(1, 0),
10718                                        lsp::Position::new(2, 0),
10719                                    ),
10720                                    "".to_string(),
10721                                )],
10722                            )]
10723                            .into_iter()
10724                            .collect(),
10725                        ),
10726                        ..Default::default()
10727                    }),
10728                    ..Default::default()
10729                },
10730            )]))
10731        })
10732        .next()
10733        .await;
10734    cx.executor().start_waiting();
10735    format.await;
10736    assert_eq!(
10737        editor.update(cx, |editor, cx| editor.text(cx)),
10738        "import { a } from 'module';\n\nconst x = a;\n"
10739    );
10740
10741    editor.update_in(cx, |editor, window, cx| {
10742        editor.set_text(
10743            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10744            window,
10745            cx,
10746        )
10747    });
10748    // Ensure we don't lock if code action hangs.
10749    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10750        move |params, _| async move {
10751            assert_eq!(
10752                params.text_document.uri,
10753                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10754            );
10755            futures::future::pending::<()>().await;
10756            unreachable!()
10757        },
10758    );
10759    let format = editor
10760        .update_in(cx, |editor, window, cx| {
10761            editor.perform_code_action_kind(
10762                project,
10763                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10764                window,
10765                cx,
10766            )
10767        })
10768        .unwrap();
10769    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10770    cx.executor().start_waiting();
10771    format.await;
10772    assert_eq!(
10773        editor.update(cx, |editor, cx| editor.text(cx)),
10774        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10775    );
10776}
10777
10778#[gpui::test]
10779async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10780    init_test(cx, |_| {});
10781
10782    let mut cx = EditorLspTestContext::new_rust(
10783        lsp::ServerCapabilities {
10784            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10785            ..Default::default()
10786        },
10787        cx,
10788    )
10789    .await;
10790
10791    cx.set_state(indoc! {"
10792        one.twoˇ
10793    "});
10794
10795    // The format request takes a long time. When it completes, it inserts
10796    // a newline and an indent before the `.`
10797    cx.lsp
10798        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10799            let executor = cx.background_executor().clone();
10800            async move {
10801                executor.timer(Duration::from_millis(100)).await;
10802                Ok(Some(vec![lsp::TextEdit {
10803                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10804                    new_text: "\n    ".into(),
10805                }]))
10806            }
10807        });
10808
10809    // Submit a format request.
10810    let format_1 = cx
10811        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10812        .unwrap();
10813    cx.executor().run_until_parked();
10814
10815    // Submit a second format request.
10816    let format_2 = cx
10817        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10818        .unwrap();
10819    cx.executor().run_until_parked();
10820
10821    // Wait for both format requests to complete
10822    cx.executor().advance_clock(Duration::from_millis(200));
10823    cx.executor().start_waiting();
10824    format_1.await.unwrap();
10825    cx.executor().start_waiting();
10826    format_2.await.unwrap();
10827
10828    // The formatting edits only happens once.
10829    cx.assert_editor_state(indoc! {"
10830        one
10831            .twoˇ
10832    "});
10833}
10834
10835#[gpui::test]
10836async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10837    init_test(cx, |settings| {
10838        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10839    });
10840
10841    let mut cx = EditorLspTestContext::new_rust(
10842        lsp::ServerCapabilities {
10843            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10844            ..Default::default()
10845        },
10846        cx,
10847    )
10848    .await;
10849
10850    // Set up a buffer white some trailing whitespace and no trailing newline.
10851    cx.set_state(
10852        &[
10853            "one ",   //
10854            "twoˇ",   //
10855            "three ", //
10856            "four",   //
10857        ]
10858        .join("\n"),
10859    );
10860
10861    // Submit a format request.
10862    let format = cx
10863        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10864        .unwrap();
10865
10866    // Record which buffer changes have been sent to the language server
10867    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10868    cx.lsp
10869        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10870            let buffer_changes = buffer_changes.clone();
10871            move |params, _| {
10872                buffer_changes.lock().extend(
10873                    params
10874                        .content_changes
10875                        .into_iter()
10876                        .map(|e| (e.range.unwrap(), e.text)),
10877                );
10878            }
10879        });
10880
10881    // Handle formatting requests to the language server.
10882    cx.lsp
10883        .set_request_handler::<lsp::request::Formatting, _, _>({
10884            let buffer_changes = buffer_changes.clone();
10885            move |_, _| {
10886                // When formatting is requested, trailing whitespace has already been stripped,
10887                // and the trailing newline has already been added.
10888                assert_eq!(
10889                    &buffer_changes.lock()[1..],
10890                    &[
10891                        (
10892                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10893                            "".into()
10894                        ),
10895                        (
10896                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10897                            "".into()
10898                        ),
10899                        (
10900                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10901                            "\n".into()
10902                        ),
10903                    ]
10904                );
10905
10906                // Insert blank lines between each line of the buffer.
10907                async move {
10908                    Ok(Some(vec![
10909                        lsp::TextEdit {
10910                            range: lsp::Range::new(
10911                                lsp::Position::new(1, 0),
10912                                lsp::Position::new(1, 0),
10913                            ),
10914                            new_text: "\n".into(),
10915                        },
10916                        lsp::TextEdit {
10917                            range: lsp::Range::new(
10918                                lsp::Position::new(2, 0),
10919                                lsp::Position::new(2, 0),
10920                            ),
10921                            new_text: "\n".into(),
10922                        },
10923                    ]))
10924                }
10925            }
10926        });
10927
10928    // After formatting the buffer, the trailing whitespace is stripped,
10929    // a newline is appended, and the edits provided by the language server
10930    // have been applied.
10931    format.await.unwrap();
10932    cx.assert_editor_state(
10933        &[
10934            "one",   //
10935            "",      //
10936            "twoˇ",  //
10937            "",      //
10938            "three", //
10939            "four",  //
10940            "",      //
10941        ]
10942        .join("\n"),
10943    );
10944
10945    // Undoing the formatting undoes the trailing whitespace removal, the
10946    // trailing newline, and the LSP edits.
10947    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10948    cx.assert_editor_state(
10949        &[
10950            "one ",   //
10951            "twoˇ",   //
10952            "three ", //
10953            "four",   //
10954        ]
10955        .join("\n"),
10956    );
10957}
10958
10959#[gpui::test]
10960async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10961    cx: &mut TestAppContext,
10962) {
10963    init_test(cx, |_| {});
10964
10965    cx.update(|cx| {
10966        cx.update_global::<SettingsStore, _>(|settings, cx| {
10967            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10968                settings.auto_signature_help = Some(true);
10969            });
10970        });
10971    });
10972
10973    let mut cx = EditorLspTestContext::new_rust(
10974        lsp::ServerCapabilities {
10975            signature_help_provider: Some(lsp::SignatureHelpOptions {
10976                ..Default::default()
10977            }),
10978            ..Default::default()
10979        },
10980        cx,
10981    )
10982    .await;
10983
10984    let language = Language::new(
10985        LanguageConfig {
10986            name: "Rust".into(),
10987            brackets: BracketPairConfig {
10988                pairs: vec![
10989                    BracketPair {
10990                        start: "{".to_string(),
10991                        end: "}".to_string(),
10992                        close: true,
10993                        surround: true,
10994                        newline: true,
10995                    },
10996                    BracketPair {
10997                        start: "(".to_string(),
10998                        end: ")".to_string(),
10999                        close: true,
11000                        surround: true,
11001                        newline: true,
11002                    },
11003                    BracketPair {
11004                        start: "/*".to_string(),
11005                        end: " */".to_string(),
11006                        close: true,
11007                        surround: true,
11008                        newline: true,
11009                    },
11010                    BracketPair {
11011                        start: "[".to_string(),
11012                        end: "]".to_string(),
11013                        close: false,
11014                        surround: false,
11015                        newline: true,
11016                    },
11017                    BracketPair {
11018                        start: "\"".to_string(),
11019                        end: "\"".to_string(),
11020                        close: true,
11021                        surround: true,
11022                        newline: false,
11023                    },
11024                    BracketPair {
11025                        start: "<".to_string(),
11026                        end: ">".to_string(),
11027                        close: false,
11028                        surround: true,
11029                        newline: true,
11030                    },
11031                ],
11032                ..Default::default()
11033            },
11034            autoclose_before: "})]".to_string(),
11035            ..Default::default()
11036        },
11037        Some(tree_sitter_rust::LANGUAGE.into()),
11038    );
11039    let language = Arc::new(language);
11040
11041    cx.language_registry().add(language.clone());
11042    cx.update_buffer(|buffer, cx| {
11043        buffer.set_language(Some(language), cx);
11044    });
11045
11046    cx.set_state(
11047        &r#"
11048            fn main() {
11049                sampleˇ
11050            }
11051        "#
11052        .unindent(),
11053    );
11054
11055    cx.update_editor(|editor, window, cx| {
11056        editor.handle_input("(", window, cx);
11057    });
11058    cx.assert_editor_state(
11059        &"
11060            fn main() {
11061                sample(ˇ)
11062            }
11063        "
11064        .unindent(),
11065    );
11066
11067    let mocked_response = lsp::SignatureHelp {
11068        signatures: vec![lsp::SignatureInformation {
11069            label: "fn sample(param1: u8, param2: u8)".to_string(),
11070            documentation: None,
11071            parameters: Some(vec![
11072                lsp::ParameterInformation {
11073                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11074                    documentation: None,
11075                },
11076                lsp::ParameterInformation {
11077                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11078                    documentation: None,
11079                },
11080            ]),
11081            active_parameter: None,
11082        }],
11083        active_signature: Some(0),
11084        active_parameter: Some(0),
11085    };
11086    handle_signature_help_request(&mut cx, mocked_response).await;
11087
11088    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11089        .await;
11090
11091    cx.editor(|editor, _, _| {
11092        let signature_help_state = editor.signature_help_state.popover().cloned();
11093        let signature = signature_help_state.unwrap();
11094        assert_eq!(
11095            signature.signatures[signature.current_signature].label,
11096            "fn sample(param1: u8, param2: u8)"
11097        );
11098    });
11099}
11100
11101#[gpui::test]
11102async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11103    init_test(cx, |_| {});
11104
11105    cx.update(|cx| {
11106        cx.update_global::<SettingsStore, _>(|settings, cx| {
11107            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11108                settings.auto_signature_help = Some(false);
11109                settings.show_signature_help_after_edits = Some(false);
11110            });
11111        });
11112    });
11113
11114    let mut cx = EditorLspTestContext::new_rust(
11115        lsp::ServerCapabilities {
11116            signature_help_provider: Some(lsp::SignatureHelpOptions {
11117                ..Default::default()
11118            }),
11119            ..Default::default()
11120        },
11121        cx,
11122    )
11123    .await;
11124
11125    let language = Language::new(
11126        LanguageConfig {
11127            name: "Rust".into(),
11128            brackets: BracketPairConfig {
11129                pairs: vec![
11130                    BracketPair {
11131                        start: "{".to_string(),
11132                        end: "}".to_string(),
11133                        close: true,
11134                        surround: true,
11135                        newline: true,
11136                    },
11137                    BracketPair {
11138                        start: "(".to_string(),
11139                        end: ")".to_string(),
11140                        close: true,
11141                        surround: true,
11142                        newline: true,
11143                    },
11144                    BracketPair {
11145                        start: "/*".to_string(),
11146                        end: " */".to_string(),
11147                        close: true,
11148                        surround: true,
11149                        newline: true,
11150                    },
11151                    BracketPair {
11152                        start: "[".to_string(),
11153                        end: "]".to_string(),
11154                        close: false,
11155                        surround: false,
11156                        newline: true,
11157                    },
11158                    BracketPair {
11159                        start: "\"".to_string(),
11160                        end: "\"".to_string(),
11161                        close: true,
11162                        surround: true,
11163                        newline: false,
11164                    },
11165                    BracketPair {
11166                        start: "<".to_string(),
11167                        end: ">".to_string(),
11168                        close: false,
11169                        surround: true,
11170                        newline: true,
11171                    },
11172                ],
11173                ..Default::default()
11174            },
11175            autoclose_before: "})]".to_string(),
11176            ..Default::default()
11177        },
11178        Some(tree_sitter_rust::LANGUAGE.into()),
11179    );
11180    let language = Arc::new(language);
11181
11182    cx.language_registry().add(language.clone());
11183    cx.update_buffer(|buffer, cx| {
11184        buffer.set_language(Some(language), cx);
11185    });
11186
11187    // Ensure that signature_help is not called when no signature help is enabled.
11188    cx.set_state(
11189        &r#"
11190            fn main() {
11191                sampleˇ
11192            }
11193        "#
11194        .unindent(),
11195    );
11196    cx.update_editor(|editor, window, cx| {
11197        editor.handle_input("(", window, cx);
11198    });
11199    cx.assert_editor_state(
11200        &"
11201            fn main() {
11202                sample(ˇ)
11203            }
11204        "
11205        .unindent(),
11206    );
11207    cx.editor(|editor, _, _| {
11208        assert!(editor.signature_help_state.task().is_none());
11209    });
11210
11211    let mocked_response = lsp::SignatureHelp {
11212        signatures: vec![lsp::SignatureInformation {
11213            label: "fn sample(param1: u8, param2: u8)".to_string(),
11214            documentation: None,
11215            parameters: Some(vec![
11216                lsp::ParameterInformation {
11217                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11218                    documentation: None,
11219                },
11220                lsp::ParameterInformation {
11221                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11222                    documentation: None,
11223                },
11224            ]),
11225            active_parameter: None,
11226        }],
11227        active_signature: Some(0),
11228        active_parameter: Some(0),
11229    };
11230
11231    // Ensure that signature_help is called when enabled afte edits
11232    cx.update(|_, cx| {
11233        cx.update_global::<SettingsStore, _>(|settings, cx| {
11234            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11235                settings.auto_signature_help = Some(false);
11236                settings.show_signature_help_after_edits = Some(true);
11237            });
11238        });
11239    });
11240    cx.set_state(
11241        &r#"
11242            fn main() {
11243                sampleˇ
11244            }
11245        "#
11246        .unindent(),
11247    );
11248    cx.update_editor(|editor, window, cx| {
11249        editor.handle_input("(", window, cx);
11250    });
11251    cx.assert_editor_state(
11252        &"
11253            fn main() {
11254                sample(ˇ)
11255            }
11256        "
11257        .unindent(),
11258    );
11259    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11260    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11261        .await;
11262    cx.update_editor(|editor, _, _| {
11263        let signature_help_state = editor.signature_help_state.popover().cloned();
11264        assert!(signature_help_state.is_some());
11265        let signature = signature_help_state.unwrap();
11266        assert_eq!(
11267            signature.signatures[signature.current_signature].label,
11268            "fn sample(param1: u8, param2: u8)"
11269        );
11270        editor.signature_help_state = SignatureHelpState::default();
11271    });
11272
11273    // Ensure that signature_help is called when auto signature help override is enabled
11274    cx.update(|_, cx| {
11275        cx.update_global::<SettingsStore, _>(|settings, cx| {
11276            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11277                settings.auto_signature_help = Some(true);
11278                settings.show_signature_help_after_edits = Some(false);
11279            });
11280        });
11281    });
11282    cx.set_state(
11283        &r#"
11284            fn main() {
11285                sampleˇ
11286            }
11287        "#
11288        .unindent(),
11289    );
11290    cx.update_editor(|editor, window, cx| {
11291        editor.handle_input("(", window, cx);
11292    });
11293    cx.assert_editor_state(
11294        &"
11295            fn main() {
11296                sample(ˇ)
11297            }
11298        "
11299        .unindent(),
11300    );
11301    handle_signature_help_request(&mut cx, mocked_response).await;
11302    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11303        .await;
11304    cx.editor(|editor, _, _| {
11305        let signature_help_state = editor.signature_help_state.popover().cloned();
11306        assert!(signature_help_state.is_some());
11307        let signature = signature_help_state.unwrap();
11308        assert_eq!(
11309            signature.signatures[signature.current_signature].label,
11310            "fn sample(param1: u8, param2: u8)"
11311        );
11312    });
11313}
11314
11315#[gpui::test]
11316async fn test_signature_help(cx: &mut TestAppContext) {
11317    init_test(cx, |_| {});
11318    cx.update(|cx| {
11319        cx.update_global::<SettingsStore, _>(|settings, cx| {
11320            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11321                settings.auto_signature_help = Some(true);
11322            });
11323        });
11324    });
11325
11326    let mut cx = EditorLspTestContext::new_rust(
11327        lsp::ServerCapabilities {
11328            signature_help_provider: Some(lsp::SignatureHelpOptions {
11329                ..Default::default()
11330            }),
11331            ..Default::default()
11332        },
11333        cx,
11334    )
11335    .await;
11336
11337    // A test that directly calls `show_signature_help`
11338    cx.update_editor(|editor, window, cx| {
11339        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11340    });
11341
11342    let mocked_response = lsp::SignatureHelp {
11343        signatures: vec![lsp::SignatureInformation {
11344            label: "fn sample(param1: u8, param2: u8)".to_string(),
11345            documentation: None,
11346            parameters: Some(vec![
11347                lsp::ParameterInformation {
11348                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11349                    documentation: None,
11350                },
11351                lsp::ParameterInformation {
11352                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11353                    documentation: None,
11354                },
11355            ]),
11356            active_parameter: None,
11357        }],
11358        active_signature: Some(0),
11359        active_parameter: Some(0),
11360    };
11361    handle_signature_help_request(&mut cx, mocked_response).await;
11362
11363    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11364        .await;
11365
11366    cx.editor(|editor, _, _| {
11367        let signature_help_state = editor.signature_help_state.popover().cloned();
11368        assert!(signature_help_state.is_some());
11369        let signature = signature_help_state.unwrap();
11370        assert_eq!(
11371            signature.signatures[signature.current_signature].label,
11372            "fn sample(param1: u8, param2: u8)"
11373        );
11374    });
11375
11376    // When exiting outside from inside the brackets, `signature_help` is closed.
11377    cx.set_state(indoc! {"
11378        fn main() {
11379            sample(ˇ);
11380        }
11381
11382        fn sample(param1: u8, param2: u8) {}
11383    "});
11384
11385    cx.update_editor(|editor, window, cx| {
11386        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11387            s.select_ranges([0..0])
11388        });
11389    });
11390
11391    let mocked_response = lsp::SignatureHelp {
11392        signatures: Vec::new(),
11393        active_signature: None,
11394        active_parameter: None,
11395    };
11396    handle_signature_help_request(&mut cx, mocked_response).await;
11397
11398    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11399        .await;
11400
11401    cx.editor(|editor, _, _| {
11402        assert!(!editor.signature_help_state.is_shown());
11403    });
11404
11405    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11406    cx.set_state(indoc! {"
11407        fn main() {
11408            sample(ˇ);
11409        }
11410
11411        fn sample(param1: u8, param2: u8) {}
11412    "});
11413
11414    let mocked_response = lsp::SignatureHelp {
11415        signatures: vec![lsp::SignatureInformation {
11416            label: "fn sample(param1: u8, param2: u8)".to_string(),
11417            documentation: None,
11418            parameters: Some(vec![
11419                lsp::ParameterInformation {
11420                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11421                    documentation: None,
11422                },
11423                lsp::ParameterInformation {
11424                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11425                    documentation: None,
11426                },
11427            ]),
11428            active_parameter: None,
11429        }],
11430        active_signature: Some(0),
11431        active_parameter: Some(0),
11432    };
11433    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11434    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11435        .await;
11436    cx.editor(|editor, _, _| {
11437        assert!(editor.signature_help_state.is_shown());
11438    });
11439
11440    // Restore the popover with more parameter input
11441    cx.set_state(indoc! {"
11442        fn main() {
11443            sample(param1, param2ˇ);
11444        }
11445
11446        fn sample(param1: u8, param2: u8) {}
11447    "});
11448
11449    let mocked_response = lsp::SignatureHelp {
11450        signatures: vec![lsp::SignatureInformation {
11451            label: "fn sample(param1: u8, param2: u8)".to_string(),
11452            documentation: None,
11453            parameters: Some(vec![
11454                lsp::ParameterInformation {
11455                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11456                    documentation: None,
11457                },
11458                lsp::ParameterInformation {
11459                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11460                    documentation: None,
11461                },
11462            ]),
11463            active_parameter: None,
11464        }],
11465        active_signature: Some(0),
11466        active_parameter: Some(1),
11467    };
11468    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11469    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11470        .await;
11471
11472    // When selecting a range, the popover is gone.
11473    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11474    cx.update_editor(|editor, window, cx| {
11475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11476            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11477        })
11478    });
11479    cx.assert_editor_state(indoc! {"
11480        fn main() {
11481            sample(param1, «ˇparam2»);
11482        }
11483
11484        fn sample(param1: u8, param2: u8) {}
11485    "});
11486    cx.editor(|editor, _, _| {
11487        assert!(!editor.signature_help_state.is_shown());
11488    });
11489
11490    // When unselecting again, the popover is back if within the brackets.
11491    cx.update_editor(|editor, window, cx| {
11492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11493            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11494        })
11495    });
11496    cx.assert_editor_state(indoc! {"
11497        fn main() {
11498            sample(param1, ˇparam2);
11499        }
11500
11501        fn sample(param1: u8, param2: u8) {}
11502    "});
11503    handle_signature_help_request(&mut cx, mocked_response).await;
11504    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11505        .await;
11506    cx.editor(|editor, _, _| {
11507        assert!(editor.signature_help_state.is_shown());
11508    });
11509
11510    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11511    cx.update_editor(|editor, window, cx| {
11512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11513            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11514            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11515        })
11516    });
11517    cx.assert_editor_state(indoc! {"
11518        fn main() {
11519            sample(param1, ˇparam2);
11520        }
11521
11522        fn sample(param1: u8, param2: u8) {}
11523    "});
11524
11525    let mocked_response = lsp::SignatureHelp {
11526        signatures: vec![lsp::SignatureInformation {
11527            label: "fn sample(param1: u8, param2: u8)".to_string(),
11528            documentation: None,
11529            parameters: Some(vec![
11530                lsp::ParameterInformation {
11531                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11532                    documentation: None,
11533                },
11534                lsp::ParameterInformation {
11535                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11536                    documentation: None,
11537                },
11538            ]),
11539            active_parameter: None,
11540        }],
11541        active_signature: Some(0),
11542        active_parameter: Some(1),
11543    };
11544    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11545    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11546        .await;
11547    cx.update_editor(|editor, _, cx| {
11548        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11549    });
11550    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11551        .await;
11552    cx.update_editor(|editor, window, cx| {
11553        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11554            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11555        })
11556    });
11557    cx.assert_editor_state(indoc! {"
11558        fn main() {
11559            sample(param1, «ˇparam2»);
11560        }
11561
11562        fn sample(param1: u8, param2: u8) {}
11563    "});
11564    cx.update_editor(|editor, window, cx| {
11565        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11566            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11567        })
11568    });
11569    cx.assert_editor_state(indoc! {"
11570        fn main() {
11571            sample(param1, ˇparam2);
11572        }
11573
11574        fn sample(param1: u8, param2: u8) {}
11575    "});
11576    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11577        .await;
11578}
11579
11580#[gpui::test]
11581async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11582    init_test(cx, |_| {});
11583
11584    let mut cx = EditorLspTestContext::new_rust(
11585        lsp::ServerCapabilities {
11586            signature_help_provider: Some(lsp::SignatureHelpOptions {
11587                ..Default::default()
11588            }),
11589            ..Default::default()
11590        },
11591        cx,
11592    )
11593    .await;
11594
11595    cx.set_state(indoc! {"
11596        fn main() {
11597            overloadedˇ
11598        }
11599    "});
11600
11601    cx.update_editor(|editor, window, cx| {
11602        editor.handle_input("(", window, cx);
11603        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11604    });
11605
11606    // Mock response with 3 signatures
11607    let mocked_response = lsp::SignatureHelp {
11608        signatures: vec![
11609            lsp::SignatureInformation {
11610                label: "fn overloaded(x: i32)".to_string(),
11611                documentation: None,
11612                parameters: Some(vec![lsp::ParameterInformation {
11613                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11614                    documentation: None,
11615                }]),
11616                active_parameter: None,
11617            },
11618            lsp::SignatureInformation {
11619                label: "fn overloaded(x: i32, y: i32)".to_string(),
11620                documentation: None,
11621                parameters: Some(vec![
11622                    lsp::ParameterInformation {
11623                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11624                        documentation: None,
11625                    },
11626                    lsp::ParameterInformation {
11627                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11628                        documentation: None,
11629                    },
11630                ]),
11631                active_parameter: None,
11632            },
11633            lsp::SignatureInformation {
11634                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11635                documentation: None,
11636                parameters: Some(vec![
11637                    lsp::ParameterInformation {
11638                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11639                        documentation: None,
11640                    },
11641                    lsp::ParameterInformation {
11642                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11643                        documentation: None,
11644                    },
11645                    lsp::ParameterInformation {
11646                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11647                        documentation: None,
11648                    },
11649                ]),
11650                active_parameter: None,
11651            },
11652        ],
11653        active_signature: Some(1),
11654        active_parameter: Some(0),
11655    };
11656    handle_signature_help_request(&mut cx, mocked_response).await;
11657
11658    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11659        .await;
11660
11661    // Verify we have multiple signatures and the right one is selected
11662    cx.editor(|editor, _, _| {
11663        let popover = editor.signature_help_state.popover().cloned().unwrap();
11664        assert_eq!(popover.signatures.len(), 3);
11665        // active_signature was 1, so that should be the current
11666        assert_eq!(popover.current_signature, 1);
11667        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11668        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11669        assert_eq!(
11670            popover.signatures[2].label,
11671            "fn overloaded(x: i32, y: i32, z: i32)"
11672        );
11673    });
11674
11675    // Test navigation functionality
11676    cx.update_editor(|editor, window, cx| {
11677        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11678    });
11679
11680    cx.editor(|editor, _, _| {
11681        let popover = editor.signature_help_state.popover().cloned().unwrap();
11682        assert_eq!(popover.current_signature, 2);
11683    });
11684
11685    // Test wrap around
11686    cx.update_editor(|editor, window, cx| {
11687        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11688    });
11689
11690    cx.editor(|editor, _, _| {
11691        let popover = editor.signature_help_state.popover().cloned().unwrap();
11692        assert_eq!(popover.current_signature, 0);
11693    });
11694
11695    // Test previous navigation
11696    cx.update_editor(|editor, window, cx| {
11697        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11698    });
11699
11700    cx.editor(|editor, _, _| {
11701        let popover = editor.signature_help_state.popover().cloned().unwrap();
11702        assert_eq!(popover.current_signature, 2);
11703    });
11704}
11705
11706#[gpui::test]
11707async fn test_completion_mode(cx: &mut TestAppContext) {
11708    init_test(cx, |_| {});
11709    let mut cx = EditorLspTestContext::new_rust(
11710        lsp::ServerCapabilities {
11711            completion_provider: Some(lsp::CompletionOptions {
11712                resolve_provider: Some(true),
11713                ..Default::default()
11714            }),
11715            ..Default::default()
11716        },
11717        cx,
11718    )
11719    .await;
11720
11721    struct Run {
11722        run_description: &'static str,
11723        initial_state: String,
11724        buffer_marked_text: String,
11725        completion_label: &'static str,
11726        completion_text: &'static str,
11727        expected_with_insert_mode: String,
11728        expected_with_replace_mode: String,
11729        expected_with_replace_subsequence_mode: String,
11730        expected_with_replace_suffix_mode: String,
11731    }
11732
11733    let runs = [
11734        Run {
11735            run_description: "Start of word matches completion text",
11736            initial_state: "before ediˇ after".into(),
11737            buffer_marked_text: "before <edi|> after".into(),
11738            completion_label: "editor",
11739            completion_text: "editor",
11740            expected_with_insert_mode: "before editorˇ after".into(),
11741            expected_with_replace_mode: "before editorˇ after".into(),
11742            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11743            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11744        },
11745        Run {
11746            run_description: "Accept same text at the middle of the word",
11747            initial_state: "before ediˇtor after".into(),
11748            buffer_marked_text: "before <edi|tor> after".into(),
11749            completion_label: "editor",
11750            completion_text: "editor",
11751            expected_with_insert_mode: "before editorˇtor after".into(),
11752            expected_with_replace_mode: "before editorˇ after".into(),
11753            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11754            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11755        },
11756        Run {
11757            run_description: "End of word matches completion text -- cursor at end",
11758            initial_state: "before torˇ after".into(),
11759            buffer_marked_text: "before <tor|> after".into(),
11760            completion_label: "editor",
11761            completion_text: "editor",
11762            expected_with_insert_mode: "before editorˇ after".into(),
11763            expected_with_replace_mode: "before editorˇ after".into(),
11764            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11765            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11766        },
11767        Run {
11768            run_description: "End of word matches completion text -- cursor at start",
11769            initial_state: "before ˇtor after".into(),
11770            buffer_marked_text: "before <|tor> after".into(),
11771            completion_label: "editor",
11772            completion_text: "editor",
11773            expected_with_insert_mode: "before editorˇtor after".into(),
11774            expected_with_replace_mode: "before editorˇ after".into(),
11775            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11776            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11777        },
11778        Run {
11779            run_description: "Prepend text containing whitespace",
11780            initial_state: "pˇfield: bool".into(),
11781            buffer_marked_text: "<p|field>: bool".into(),
11782            completion_label: "pub ",
11783            completion_text: "pub ",
11784            expected_with_insert_mode: "pub ˇfield: bool".into(),
11785            expected_with_replace_mode: "pub ˇ: bool".into(),
11786            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11787            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11788        },
11789        Run {
11790            run_description: "Add element to start of list",
11791            initial_state: "[element_ˇelement_2]".into(),
11792            buffer_marked_text: "[<element_|element_2>]".into(),
11793            completion_label: "element_1",
11794            completion_text: "element_1",
11795            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11796            expected_with_replace_mode: "[element_1ˇ]".into(),
11797            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11798            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11799        },
11800        Run {
11801            run_description: "Add element to start of list -- first and second elements are equal",
11802            initial_state: "[elˇelement]".into(),
11803            buffer_marked_text: "[<el|element>]".into(),
11804            completion_label: "element",
11805            completion_text: "element",
11806            expected_with_insert_mode: "[elementˇelement]".into(),
11807            expected_with_replace_mode: "[elementˇ]".into(),
11808            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11809            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11810        },
11811        Run {
11812            run_description: "Ends with matching suffix",
11813            initial_state: "SubˇError".into(),
11814            buffer_marked_text: "<Sub|Error>".into(),
11815            completion_label: "SubscriptionError",
11816            completion_text: "SubscriptionError",
11817            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11818            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11819            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11820            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11821        },
11822        Run {
11823            run_description: "Suffix is a subsequence -- contiguous",
11824            initial_state: "SubˇErr".into(),
11825            buffer_marked_text: "<Sub|Err>".into(),
11826            completion_label: "SubscriptionError",
11827            completion_text: "SubscriptionError",
11828            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11829            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11830            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11831            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11832        },
11833        Run {
11834            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11835            initial_state: "Suˇscrirr".into(),
11836            buffer_marked_text: "<Su|scrirr>".into(),
11837            completion_label: "SubscriptionError",
11838            completion_text: "SubscriptionError",
11839            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11840            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11841            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11842            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11843        },
11844        Run {
11845            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11846            initial_state: "foo(indˇix)".into(),
11847            buffer_marked_text: "foo(<ind|ix>)".into(),
11848            completion_label: "node_index",
11849            completion_text: "node_index",
11850            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11851            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11852            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11853            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11854        },
11855        Run {
11856            run_description: "Replace range ends before cursor - should extend to cursor",
11857            initial_state: "before editˇo after".into(),
11858            buffer_marked_text: "before <{ed}>it|o after".into(),
11859            completion_label: "editor",
11860            completion_text: "editor",
11861            expected_with_insert_mode: "before editorˇo after".into(),
11862            expected_with_replace_mode: "before editorˇo after".into(),
11863            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11864            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11865        },
11866        Run {
11867            run_description: "Uses label for suffix matching",
11868            initial_state: "before ediˇtor after".into(),
11869            buffer_marked_text: "before <edi|tor> after".into(),
11870            completion_label: "editor",
11871            completion_text: "editor()",
11872            expected_with_insert_mode: "before editor()ˇtor after".into(),
11873            expected_with_replace_mode: "before editor()ˇ after".into(),
11874            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11875            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11876        },
11877        Run {
11878            run_description: "Case insensitive subsequence and suffix matching",
11879            initial_state: "before EDiˇtoR after".into(),
11880            buffer_marked_text: "before <EDi|toR> after".into(),
11881            completion_label: "editor",
11882            completion_text: "editor",
11883            expected_with_insert_mode: "before editorˇtoR after".into(),
11884            expected_with_replace_mode: "before editorˇ after".into(),
11885            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11886            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11887        },
11888    ];
11889
11890    for run in runs {
11891        let run_variations = [
11892            (LspInsertMode::Insert, run.expected_with_insert_mode),
11893            (LspInsertMode::Replace, run.expected_with_replace_mode),
11894            (
11895                LspInsertMode::ReplaceSubsequence,
11896                run.expected_with_replace_subsequence_mode,
11897            ),
11898            (
11899                LspInsertMode::ReplaceSuffix,
11900                run.expected_with_replace_suffix_mode,
11901            ),
11902        ];
11903
11904        for (lsp_insert_mode, expected_text) in run_variations {
11905            eprintln!(
11906                "run = {:?}, mode = {lsp_insert_mode:.?}",
11907                run.run_description,
11908            );
11909
11910            update_test_language_settings(&mut cx, |settings| {
11911                settings.defaults.completions = Some(CompletionSettings {
11912                    lsp_insert_mode,
11913                    words: WordsCompletionMode::Disabled,
11914                    lsp: true,
11915                    lsp_fetch_timeout_ms: 0,
11916                });
11917            });
11918
11919            cx.set_state(&run.initial_state);
11920            cx.update_editor(|editor, window, cx| {
11921                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11922            });
11923
11924            let counter = Arc::new(AtomicUsize::new(0));
11925            handle_completion_request_with_insert_and_replace(
11926                &mut cx,
11927                &run.buffer_marked_text,
11928                vec![(run.completion_label, run.completion_text)],
11929                counter.clone(),
11930            )
11931            .await;
11932            cx.condition(|editor, _| editor.context_menu_visible())
11933                .await;
11934            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11935
11936            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11937                editor
11938                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11939                    .unwrap()
11940            });
11941            cx.assert_editor_state(&expected_text);
11942            handle_resolve_completion_request(&mut cx, None).await;
11943            apply_additional_edits.await.unwrap();
11944        }
11945    }
11946}
11947
11948#[gpui::test]
11949async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11950    init_test(cx, |_| {});
11951    let mut cx = EditorLspTestContext::new_rust(
11952        lsp::ServerCapabilities {
11953            completion_provider: Some(lsp::CompletionOptions {
11954                resolve_provider: Some(true),
11955                ..Default::default()
11956            }),
11957            ..Default::default()
11958        },
11959        cx,
11960    )
11961    .await;
11962
11963    let initial_state = "SubˇError";
11964    let buffer_marked_text = "<Sub|Error>";
11965    let completion_text = "SubscriptionError";
11966    let expected_with_insert_mode = "SubscriptionErrorˇError";
11967    let expected_with_replace_mode = "SubscriptionErrorˇ";
11968
11969    update_test_language_settings(&mut cx, |settings| {
11970        settings.defaults.completions = Some(CompletionSettings {
11971            words: WordsCompletionMode::Disabled,
11972            // set the opposite here to ensure that the action is overriding the default behavior
11973            lsp_insert_mode: LspInsertMode::Insert,
11974            lsp: true,
11975            lsp_fetch_timeout_ms: 0,
11976        });
11977    });
11978
11979    cx.set_state(initial_state);
11980    cx.update_editor(|editor, window, cx| {
11981        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11982    });
11983
11984    let counter = Arc::new(AtomicUsize::new(0));
11985    handle_completion_request_with_insert_and_replace(
11986        &mut cx,
11987        &buffer_marked_text,
11988        vec![(completion_text, completion_text)],
11989        counter.clone(),
11990    )
11991    .await;
11992    cx.condition(|editor, _| editor.context_menu_visible())
11993        .await;
11994    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11995
11996    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11997        editor
11998            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11999            .unwrap()
12000    });
12001    cx.assert_editor_state(&expected_with_replace_mode);
12002    handle_resolve_completion_request(&mut cx, None).await;
12003    apply_additional_edits.await.unwrap();
12004
12005    update_test_language_settings(&mut cx, |settings| {
12006        settings.defaults.completions = Some(CompletionSettings {
12007            words: WordsCompletionMode::Disabled,
12008            // set the opposite here to ensure that the action is overriding the default behavior
12009            lsp_insert_mode: LspInsertMode::Replace,
12010            lsp: true,
12011            lsp_fetch_timeout_ms: 0,
12012        });
12013    });
12014
12015    cx.set_state(initial_state);
12016    cx.update_editor(|editor, window, cx| {
12017        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12018    });
12019    handle_completion_request_with_insert_and_replace(
12020        &mut cx,
12021        &buffer_marked_text,
12022        vec![(completion_text, completion_text)],
12023        counter.clone(),
12024    )
12025    .await;
12026    cx.condition(|editor, _| editor.context_menu_visible())
12027        .await;
12028    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12029
12030    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12031        editor
12032            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12033            .unwrap()
12034    });
12035    cx.assert_editor_state(&expected_with_insert_mode);
12036    handle_resolve_completion_request(&mut cx, None).await;
12037    apply_additional_edits.await.unwrap();
12038}
12039
12040#[gpui::test]
12041async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12042    init_test(cx, |_| {});
12043    let mut cx = EditorLspTestContext::new_rust(
12044        lsp::ServerCapabilities {
12045            completion_provider: Some(lsp::CompletionOptions {
12046                resolve_provider: Some(true),
12047                ..Default::default()
12048            }),
12049            ..Default::default()
12050        },
12051        cx,
12052    )
12053    .await;
12054
12055    // scenario: surrounding text matches completion text
12056    let completion_text = "to_offset";
12057    let initial_state = indoc! {"
12058        1. buf.to_offˇsuffix
12059        2. buf.to_offˇsuf
12060        3. buf.to_offˇfix
12061        4. buf.to_offˇ
12062        5. into_offˇensive
12063        6. ˇsuffix
12064        7. let ˇ //
12065        8. aaˇzz
12066        9. buf.to_off«zzzzzˇ»suffix
12067        10. buf.«ˇzzzzz»suffix
12068        11. to_off«ˇzzzzz»
12069
12070        buf.to_offˇsuffix  // newest cursor
12071    "};
12072    let completion_marked_buffer = indoc! {"
12073        1. buf.to_offsuffix
12074        2. buf.to_offsuf
12075        3. buf.to_offfix
12076        4. buf.to_off
12077        5. into_offensive
12078        6. suffix
12079        7. let  //
12080        8. aazz
12081        9. buf.to_offzzzzzsuffix
12082        10. buf.zzzzzsuffix
12083        11. to_offzzzzz
12084
12085        buf.<to_off|suffix>  // newest cursor
12086    "};
12087    let expected = indoc! {"
12088        1. buf.to_offsetˇ
12089        2. buf.to_offsetˇsuf
12090        3. buf.to_offsetˇfix
12091        4. buf.to_offsetˇ
12092        5. into_offsetˇensive
12093        6. to_offsetˇsuffix
12094        7. let to_offsetˇ //
12095        8. aato_offsetˇzz
12096        9. buf.to_offsetˇ
12097        10. buf.to_offsetˇsuffix
12098        11. to_offsetˇ
12099
12100        buf.to_offsetˇ  // newest cursor
12101    "};
12102    cx.set_state(initial_state);
12103    cx.update_editor(|editor, window, cx| {
12104        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12105    });
12106    handle_completion_request_with_insert_and_replace(
12107        &mut cx,
12108        completion_marked_buffer,
12109        vec![(completion_text, completion_text)],
12110        Arc::new(AtomicUsize::new(0)),
12111    )
12112    .await;
12113    cx.condition(|editor, _| editor.context_menu_visible())
12114        .await;
12115    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12116        editor
12117            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12118            .unwrap()
12119    });
12120    cx.assert_editor_state(expected);
12121    handle_resolve_completion_request(&mut cx, None).await;
12122    apply_additional_edits.await.unwrap();
12123
12124    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12125    let completion_text = "foo_and_bar";
12126    let initial_state = indoc! {"
12127        1. ooanbˇ
12128        2. zooanbˇ
12129        3. ooanbˇz
12130        4. zooanbˇz
12131        5. ooanˇ
12132        6. oanbˇ
12133
12134        ooanbˇ
12135    "};
12136    let completion_marked_buffer = indoc! {"
12137        1. ooanb
12138        2. zooanb
12139        3. ooanbz
12140        4. zooanbz
12141        5. ooan
12142        6. oanb
12143
12144        <ooanb|>
12145    "};
12146    let expected = indoc! {"
12147        1. foo_and_barˇ
12148        2. zfoo_and_barˇ
12149        3. foo_and_barˇz
12150        4. zfoo_and_barˇz
12151        5. ooanfoo_and_barˇ
12152        6. oanbfoo_and_barˇ
12153
12154        foo_and_barˇ
12155    "};
12156    cx.set_state(initial_state);
12157    cx.update_editor(|editor, window, cx| {
12158        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12159    });
12160    handle_completion_request_with_insert_and_replace(
12161        &mut cx,
12162        completion_marked_buffer,
12163        vec![(completion_text, completion_text)],
12164        Arc::new(AtomicUsize::new(0)),
12165    )
12166    .await;
12167    cx.condition(|editor, _| editor.context_menu_visible())
12168        .await;
12169    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12170        editor
12171            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12172            .unwrap()
12173    });
12174    cx.assert_editor_state(expected);
12175    handle_resolve_completion_request(&mut cx, None).await;
12176    apply_additional_edits.await.unwrap();
12177
12178    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12179    // (expects the same as if it was inserted at the end)
12180    let completion_text = "foo_and_bar";
12181    let initial_state = indoc! {"
12182        1. ooˇanb
12183        2. zooˇanb
12184        3. ooˇanbz
12185        4. zooˇanbz
12186
12187        ooˇanb
12188    "};
12189    let completion_marked_buffer = indoc! {"
12190        1. ooanb
12191        2. zooanb
12192        3. ooanbz
12193        4. zooanbz
12194
12195        <oo|anb>
12196    "};
12197    let expected = indoc! {"
12198        1. foo_and_barˇ
12199        2. zfoo_and_barˇ
12200        3. foo_and_barˇz
12201        4. zfoo_and_barˇz
12202
12203        foo_and_barˇ
12204    "};
12205    cx.set_state(initial_state);
12206    cx.update_editor(|editor, window, cx| {
12207        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12208    });
12209    handle_completion_request_with_insert_and_replace(
12210        &mut cx,
12211        completion_marked_buffer,
12212        vec![(completion_text, completion_text)],
12213        Arc::new(AtomicUsize::new(0)),
12214    )
12215    .await;
12216    cx.condition(|editor, _| editor.context_menu_visible())
12217        .await;
12218    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12219        editor
12220            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12221            .unwrap()
12222    });
12223    cx.assert_editor_state(expected);
12224    handle_resolve_completion_request(&mut cx, None).await;
12225    apply_additional_edits.await.unwrap();
12226}
12227
12228// This used to crash
12229#[gpui::test]
12230async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12231    init_test(cx, |_| {});
12232
12233    let buffer_text = indoc! {"
12234        fn main() {
12235            10.satu;
12236
12237            //
12238            // separate cursors so they open in different excerpts (manually reproducible)
12239            //
12240
12241            10.satu20;
12242        }
12243    "};
12244    let multibuffer_text_with_selections = indoc! {"
12245        fn main() {
12246            10.satuˇ;
12247
12248            //
12249
12250            //
12251
12252            10.satuˇ20;
12253        }
12254    "};
12255    let expected_multibuffer = indoc! {"
12256        fn main() {
12257            10.saturating_sub()ˇ;
12258
12259            //
12260
12261            //
12262
12263            10.saturating_sub()ˇ;
12264        }
12265    "};
12266
12267    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12268    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12269
12270    let fs = FakeFs::new(cx.executor());
12271    fs.insert_tree(
12272        path!("/a"),
12273        json!({
12274            "main.rs": buffer_text,
12275        }),
12276    )
12277    .await;
12278
12279    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12280    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12281    language_registry.add(rust_lang());
12282    let mut fake_servers = language_registry.register_fake_lsp(
12283        "Rust",
12284        FakeLspAdapter {
12285            capabilities: lsp::ServerCapabilities {
12286                completion_provider: Some(lsp::CompletionOptions {
12287                    resolve_provider: None,
12288                    ..lsp::CompletionOptions::default()
12289                }),
12290                ..lsp::ServerCapabilities::default()
12291            },
12292            ..FakeLspAdapter::default()
12293        },
12294    );
12295    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12296    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12297    let buffer = project
12298        .update(cx, |project, cx| {
12299            project.open_local_buffer(path!("/a/main.rs"), cx)
12300        })
12301        .await
12302        .unwrap();
12303
12304    let multi_buffer = cx.new(|cx| {
12305        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12306        multi_buffer.push_excerpts(
12307            buffer.clone(),
12308            [ExcerptRange::new(0..first_excerpt_end)],
12309            cx,
12310        );
12311        multi_buffer.push_excerpts(
12312            buffer.clone(),
12313            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12314            cx,
12315        );
12316        multi_buffer
12317    });
12318
12319    let editor = workspace
12320        .update(cx, |_, window, cx| {
12321            cx.new(|cx| {
12322                Editor::new(
12323                    EditorMode::Full {
12324                        scale_ui_elements_with_buffer_font_size: false,
12325                        show_active_line_background: false,
12326                        sized_by_content: false,
12327                    },
12328                    multi_buffer.clone(),
12329                    Some(project.clone()),
12330                    window,
12331                    cx,
12332                )
12333            })
12334        })
12335        .unwrap();
12336
12337    let pane = workspace
12338        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12339        .unwrap();
12340    pane.update_in(cx, |pane, window, cx| {
12341        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12342    });
12343
12344    let fake_server = fake_servers.next().await.unwrap();
12345
12346    editor.update_in(cx, |editor, window, cx| {
12347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12348            s.select_ranges([
12349                Point::new(1, 11)..Point::new(1, 11),
12350                Point::new(7, 11)..Point::new(7, 11),
12351            ])
12352        });
12353
12354        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12355    });
12356
12357    editor.update_in(cx, |editor, window, cx| {
12358        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12359    });
12360
12361    fake_server
12362        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12363            let completion_item = lsp::CompletionItem {
12364                label: "saturating_sub()".into(),
12365                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12366                    lsp::InsertReplaceEdit {
12367                        new_text: "saturating_sub()".to_owned(),
12368                        insert: lsp::Range::new(
12369                            lsp::Position::new(7, 7),
12370                            lsp::Position::new(7, 11),
12371                        ),
12372                        replace: lsp::Range::new(
12373                            lsp::Position::new(7, 7),
12374                            lsp::Position::new(7, 13),
12375                        ),
12376                    },
12377                )),
12378                ..lsp::CompletionItem::default()
12379            };
12380
12381            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12382        })
12383        .next()
12384        .await
12385        .unwrap();
12386
12387    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12388        .await;
12389
12390    editor
12391        .update_in(cx, |editor, window, cx| {
12392            editor
12393                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12394                .unwrap()
12395        })
12396        .await
12397        .unwrap();
12398
12399    editor.update(cx, |editor, cx| {
12400        assert_text_with_selections(editor, expected_multibuffer, cx);
12401    })
12402}
12403
12404#[gpui::test]
12405async fn test_completion(cx: &mut TestAppContext) {
12406    init_test(cx, |_| {});
12407
12408    let mut cx = EditorLspTestContext::new_rust(
12409        lsp::ServerCapabilities {
12410            completion_provider: Some(lsp::CompletionOptions {
12411                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12412                resolve_provider: Some(true),
12413                ..Default::default()
12414            }),
12415            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12416            ..Default::default()
12417        },
12418        cx,
12419    )
12420    .await;
12421    let counter = Arc::new(AtomicUsize::new(0));
12422
12423    cx.set_state(indoc! {"
12424        oneˇ
12425        two
12426        three
12427    "});
12428    cx.simulate_keystroke(".");
12429    handle_completion_request(
12430        indoc! {"
12431            one.|<>
12432            two
12433            three
12434        "},
12435        vec!["first_completion", "second_completion"],
12436        true,
12437        counter.clone(),
12438        &mut cx,
12439    )
12440    .await;
12441    cx.condition(|editor, _| editor.context_menu_visible())
12442        .await;
12443    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12444
12445    let _handler = handle_signature_help_request(
12446        &mut cx,
12447        lsp::SignatureHelp {
12448            signatures: vec![lsp::SignatureInformation {
12449                label: "test signature".to_string(),
12450                documentation: None,
12451                parameters: Some(vec![lsp::ParameterInformation {
12452                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12453                    documentation: None,
12454                }]),
12455                active_parameter: None,
12456            }],
12457            active_signature: None,
12458            active_parameter: None,
12459        },
12460    );
12461    cx.update_editor(|editor, window, cx| {
12462        assert!(
12463            !editor.signature_help_state.is_shown(),
12464            "No signature help was called for"
12465        );
12466        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12467    });
12468    cx.run_until_parked();
12469    cx.update_editor(|editor, _, _| {
12470        assert!(
12471            !editor.signature_help_state.is_shown(),
12472            "No signature help should be shown when completions menu is open"
12473        );
12474    });
12475
12476    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12477        editor.context_menu_next(&Default::default(), window, cx);
12478        editor
12479            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12480            .unwrap()
12481    });
12482    cx.assert_editor_state(indoc! {"
12483        one.second_completionˇ
12484        two
12485        three
12486    "});
12487
12488    handle_resolve_completion_request(
12489        &mut cx,
12490        Some(vec![
12491            (
12492                //This overlaps with the primary completion edit which is
12493                //misbehavior from the LSP spec, test that we filter it out
12494                indoc! {"
12495                    one.second_ˇcompletion
12496                    two
12497                    threeˇ
12498                "},
12499                "overlapping additional edit",
12500            ),
12501            (
12502                indoc! {"
12503                    one.second_completion
12504                    two
12505                    threeˇ
12506                "},
12507                "\nadditional edit",
12508            ),
12509        ]),
12510    )
12511    .await;
12512    apply_additional_edits.await.unwrap();
12513    cx.assert_editor_state(indoc! {"
12514        one.second_completionˇ
12515        two
12516        three
12517        additional edit
12518    "});
12519
12520    cx.set_state(indoc! {"
12521        one.second_completion
12522        twoˇ
12523        threeˇ
12524        additional edit
12525    "});
12526    cx.simulate_keystroke(" ");
12527    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12528    cx.simulate_keystroke("s");
12529    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12530
12531    cx.assert_editor_state(indoc! {"
12532        one.second_completion
12533        two sˇ
12534        three sˇ
12535        additional edit
12536    "});
12537    handle_completion_request(
12538        indoc! {"
12539            one.second_completion
12540            two s
12541            three <s|>
12542            additional edit
12543        "},
12544        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12545        true,
12546        counter.clone(),
12547        &mut cx,
12548    )
12549    .await;
12550    cx.condition(|editor, _| editor.context_menu_visible())
12551        .await;
12552    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12553
12554    cx.simulate_keystroke("i");
12555
12556    handle_completion_request(
12557        indoc! {"
12558            one.second_completion
12559            two si
12560            three <si|>
12561            additional edit
12562        "},
12563        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12564        true,
12565        counter.clone(),
12566        &mut cx,
12567    )
12568    .await;
12569    cx.condition(|editor, _| editor.context_menu_visible())
12570        .await;
12571    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12572
12573    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12574        editor
12575            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12576            .unwrap()
12577    });
12578    cx.assert_editor_state(indoc! {"
12579        one.second_completion
12580        two sixth_completionˇ
12581        three sixth_completionˇ
12582        additional edit
12583    "});
12584
12585    apply_additional_edits.await.unwrap();
12586
12587    update_test_language_settings(&mut cx, |settings| {
12588        settings.defaults.show_completions_on_input = Some(false);
12589    });
12590    cx.set_state("editorˇ");
12591    cx.simulate_keystroke(".");
12592    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12593    cx.simulate_keystrokes("c l o");
12594    cx.assert_editor_state("editor.cloˇ");
12595    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12596    cx.update_editor(|editor, window, cx| {
12597        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12598    });
12599    handle_completion_request(
12600        "editor.<clo|>",
12601        vec!["close", "clobber"],
12602        true,
12603        counter.clone(),
12604        &mut cx,
12605    )
12606    .await;
12607    cx.condition(|editor, _| editor.context_menu_visible())
12608        .await;
12609    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12610
12611    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12612        editor
12613            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12614            .unwrap()
12615    });
12616    cx.assert_editor_state("editor.clobberˇ");
12617    handle_resolve_completion_request(&mut cx, None).await;
12618    apply_additional_edits.await.unwrap();
12619}
12620
12621#[gpui::test]
12622async fn test_completion_reuse(cx: &mut TestAppContext) {
12623    init_test(cx, |_| {});
12624
12625    let mut cx = EditorLspTestContext::new_rust(
12626        lsp::ServerCapabilities {
12627            completion_provider: Some(lsp::CompletionOptions {
12628                trigger_characters: Some(vec![".".to_string()]),
12629                ..Default::default()
12630            }),
12631            ..Default::default()
12632        },
12633        cx,
12634    )
12635    .await;
12636
12637    let counter = Arc::new(AtomicUsize::new(0));
12638    cx.set_state("objˇ");
12639    cx.simulate_keystroke(".");
12640
12641    // Initial completion request returns complete results
12642    let is_incomplete = false;
12643    handle_completion_request(
12644        "obj.|<>",
12645        vec!["a", "ab", "abc"],
12646        is_incomplete,
12647        counter.clone(),
12648        &mut cx,
12649    )
12650    .await;
12651    cx.run_until_parked();
12652    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12653    cx.assert_editor_state("obj.ˇ");
12654    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12655
12656    // Type "a" - filters existing completions
12657    cx.simulate_keystroke("a");
12658    cx.run_until_parked();
12659    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12660    cx.assert_editor_state("obj.aˇ");
12661    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12662
12663    // Type "b" - filters existing completions
12664    cx.simulate_keystroke("b");
12665    cx.run_until_parked();
12666    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12667    cx.assert_editor_state("obj.abˇ");
12668    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12669
12670    // Type "c" - filters existing completions
12671    cx.simulate_keystroke("c");
12672    cx.run_until_parked();
12673    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12674    cx.assert_editor_state("obj.abcˇ");
12675    check_displayed_completions(vec!["abc"], &mut cx);
12676
12677    // Backspace to delete "c" - filters existing completions
12678    cx.update_editor(|editor, window, cx| {
12679        editor.backspace(&Backspace, window, cx);
12680    });
12681    cx.run_until_parked();
12682    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12683    cx.assert_editor_state("obj.abˇ");
12684    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12685
12686    // Moving cursor to the left dismisses menu.
12687    cx.update_editor(|editor, window, cx| {
12688        editor.move_left(&MoveLeft, window, cx);
12689    });
12690    cx.run_until_parked();
12691    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12692    cx.assert_editor_state("obj.aˇb");
12693    cx.update_editor(|editor, _, _| {
12694        assert_eq!(editor.context_menu_visible(), false);
12695    });
12696
12697    // Type "b" - new request
12698    cx.simulate_keystroke("b");
12699    let is_incomplete = false;
12700    handle_completion_request(
12701        "obj.<ab|>a",
12702        vec!["ab", "abc"],
12703        is_incomplete,
12704        counter.clone(),
12705        &mut cx,
12706    )
12707    .await;
12708    cx.run_until_parked();
12709    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12710    cx.assert_editor_state("obj.abˇb");
12711    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12712
12713    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12714    cx.update_editor(|editor, window, cx| {
12715        editor.backspace(&Backspace, window, cx);
12716    });
12717    let is_incomplete = false;
12718    handle_completion_request(
12719        "obj.<a|>b",
12720        vec!["a", "ab", "abc"],
12721        is_incomplete,
12722        counter.clone(),
12723        &mut cx,
12724    )
12725    .await;
12726    cx.run_until_parked();
12727    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12728    cx.assert_editor_state("obj.aˇb");
12729    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12730
12731    // Backspace to delete "a" - dismisses menu.
12732    cx.update_editor(|editor, window, cx| {
12733        editor.backspace(&Backspace, window, cx);
12734    });
12735    cx.run_until_parked();
12736    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12737    cx.assert_editor_state("obj.ˇb");
12738    cx.update_editor(|editor, _, _| {
12739        assert_eq!(editor.context_menu_visible(), false);
12740    });
12741}
12742
12743#[gpui::test]
12744async fn test_word_completion(cx: &mut TestAppContext) {
12745    let lsp_fetch_timeout_ms = 10;
12746    init_test(cx, |language_settings| {
12747        language_settings.defaults.completions = Some(CompletionSettings {
12748            words: WordsCompletionMode::Fallback,
12749            lsp: true,
12750            lsp_fetch_timeout_ms: 10,
12751            lsp_insert_mode: LspInsertMode::Insert,
12752        });
12753    });
12754
12755    let mut cx = EditorLspTestContext::new_rust(
12756        lsp::ServerCapabilities {
12757            completion_provider: Some(lsp::CompletionOptions {
12758                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12759                ..lsp::CompletionOptions::default()
12760            }),
12761            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12762            ..lsp::ServerCapabilities::default()
12763        },
12764        cx,
12765    )
12766    .await;
12767
12768    let throttle_completions = Arc::new(AtomicBool::new(false));
12769
12770    let lsp_throttle_completions = throttle_completions.clone();
12771    let _completion_requests_handler =
12772        cx.lsp
12773            .server
12774            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12775                let lsp_throttle_completions = lsp_throttle_completions.clone();
12776                let cx = cx.clone();
12777                async move {
12778                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12779                        cx.background_executor()
12780                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12781                            .await;
12782                    }
12783                    Ok(Some(lsp::CompletionResponse::Array(vec![
12784                        lsp::CompletionItem {
12785                            label: "first".into(),
12786                            ..lsp::CompletionItem::default()
12787                        },
12788                        lsp::CompletionItem {
12789                            label: "last".into(),
12790                            ..lsp::CompletionItem::default()
12791                        },
12792                    ])))
12793                }
12794            });
12795
12796    cx.set_state(indoc! {"
12797        oneˇ
12798        two
12799        three
12800    "});
12801    cx.simulate_keystroke(".");
12802    cx.executor().run_until_parked();
12803    cx.condition(|editor, _| editor.context_menu_visible())
12804        .await;
12805    cx.update_editor(|editor, window, cx| {
12806        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12807        {
12808            assert_eq!(
12809                completion_menu_entries(&menu),
12810                &["first", "last"],
12811                "When LSP server is fast to reply, no fallback word completions are used"
12812            );
12813        } else {
12814            panic!("expected completion menu to be open");
12815        }
12816        editor.cancel(&Cancel, window, cx);
12817    });
12818    cx.executor().run_until_parked();
12819    cx.condition(|editor, _| !editor.context_menu_visible())
12820        .await;
12821
12822    throttle_completions.store(true, atomic::Ordering::Release);
12823    cx.simulate_keystroke(".");
12824    cx.executor()
12825        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12826    cx.executor().run_until_parked();
12827    cx.condition(|editor, _| editor.context_menu_visible())
12828        .await;
12829    cx.update_editor(|editor, _, _| {
12830        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12831        {
12832            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12833                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12834        } else {
12835            panic!("expected completion menu to be open");
12836        }
12837    });
12838}
12839
12840#[gpui::test]
12841async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12842    init_test(cx, |language_settings| {
12843        language_settings.defaults.completions = Some(CompletionSettings {
12844            words: WordsCompletionMode::Enabled,
12845            lsp: true,
12846            lsp_fetch_timeout_ms: 0,
12847            lsp_insert_mode: LspInsertMode::Insert,
12848        });
12849    });
12850
12851    let mut cx = EditorLspTestContext::new_rust(
12852        lsp::ServerCapabilities {
12853            completion_provider: Some(lsp::CompletionOptions {
12854                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12855                ..lsp::CompletionOptions::default()
12856            }),
12857            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12858            ..lsp::ServerCapabilities::default()
12859        },
12860        cx,
12861    )
12862    .await;
12863
12864    let _completion_requests_handler =
12865        cx.lsp
12866            .server
12867            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12868                Ok(Some(lsp::CompletionResponse::Array(vec![
12869                    lsp::CompletionItem {
12870                        label: "first".into(),
12871                        ..lsp::CompletionItem::default()
12872                    },
12873                    lsp::CompletionItem {
12874                        label: "last".into(),
12875                        ..lsp::CompletionItem::default()
12876                    },
12877                ])))
12878            });
12879
12880    cx.set_state(indoc! {"ˇ
12881        first
12882        last
12883        second
12884    "});
12885    cx.simulate_keystroke(".");
12886    cx.executor().run_until_parked();
12887    cx.condition(|editor, _| editor.context_menu_visible())
12888        .await;
12889    cx.update_editor(|editor, _, _| {
12890        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12891        {
12892            assert_eq!(
12893                completion_menu_entries(&menu),
12894                &["first", "last", "second"],
12895                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12896            );
12897        } else {
12898            panic!("expected completion menu to be open");
12899        }
12900    });
12901}
12902
12903#[gpui::test]
12904async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12905    init_test(cx, |language_settings| {
12906        language_settings.defaults.completions = Some(CompletionSettings {
12907            words: WordsCompletionMode::Disabled,
12908            lsp: true,
12909            lsp_fetch_timeout_ms: 0,
12910            lsp_insert_mode: LspInsertMode::Insert,
12911        });
12912    });
12913
12914    let mut cx = EditorLspTestContext::new_rust(
12915        lsp::ServerCapabilities {
12916            completion_provider: Some(lsp::CompletionOptions {
12917                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12918                ..lsp::CompletionOptions::default()
12919            }),
12920            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12921            ..lsp::ServerCapabilities::default()
12922        },
12923        cx,
12924    )
12925    .await;
12926
12927    let _completion_requests_handler =
12928        cx.lsp
12929            .server
12930            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12931                panic!("LSP completions should not be queried when dealing with word completions")
12932            });
12933
12934    cx.set_state(indoc! {"ˇ
12935        first
12936        last
12937        second
12938    "});
12939    cx.update_editor(|editor, window, cx| {
12940        editor.show_word_completions(&ShowWordCompletions, window, cx);
12941    });
12942    cx.executor().run_until_parked();
12943    cx.condition(|editor, _| editor.context_menu_visible())
12944        .await;
12945    cx.update_editor(|editor, _, _| {
12946        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12947        {
12948            assert_eq!(
12949                completion_menu_entries(&menu),
12950                &["first", "last", "second"],
12951                "`ShowWordCompletions` action should show word completions"
12952            );
12953        } else {
12954            panic!("expected completion menu to be open");
12955        }
12956    });
12957
12958    cx.simulate_keystroke("l");
12959    cx.executor().run_until_parked();
12960    cx.condition(|editor, _| editor.context_menu_visible())
12961        .await;
12962    cx.update_editor(|editor, _, _| {
12963        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12964        {
12965            assert_eq!(
12966                completion_menu_entries(&menu),
12967                &["last"],
12968                "After showing word completions, further editing should filter them and not query the LSP"
12969            );
12970        } else {
12971            panic!("expected completion menu to be open");
12972        }
12973    });
12974}
12975
12976#[gpui::test]
12977async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12978    init_test(cx, |language_settings| {
12979        language_settings.defaults.completions = Some(CompletionSettings {
12980            words: WordsCompletionMode::Fallback,
12981            lsp: false,
12982            lsp_fetch_timeout_ms: 0,
12983            lsp_insert_mode: LspInsertMode::Insert,
12984        });
12985    });
12986
12987    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12988
12989    cx.set_state(indoc! {"ˇ
12990        0_usize
12991        let
12992        33
12993        4.5f32
12994    "});
12995    cx.update_editor(|editor, window, cx| {
12996        editor.show_completions(&ShowCompletions::default(), window, cx);
12997    });
12998    cx.executor().run_until_parked();
12999    cx.condition(|editor, _| editor.context_menu_visible())
13000        .await;
13001    cx.update_editor(|editor, window, cx| {
13002        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13003        {
13004            assert_eq!(
13005                completion_menu_entries(&menu),
13006                &["let"],
13007                "With no digits in the completion query, no digits should be in the word completions"
13008            );
13009        } else {
13010            panic!("expected completion menu to be open");
13011        }
13012        editor.cancel(&Cancel, window, cx);
13013    });
13014
13015    cx.set_state(indoc! {"13016        0_usize
13017        let
13018        3
13019        33.35f32
13020    "});
13021    cx.update_editor(|editor, window, cx| {
13022        editor.show_completions(&ShowCompletions::default(), window, cx);
13023    });
13024    cx.executor().run_until_parked();
13025    cx.condition(|editor, _| editor.context_menu_visible())
13026        .await;
13027    cx.update_editor(|editor, _, _| {
13028        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13029        {
13030            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13031                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13032        } else {
13033            panic!("expected completion menu to be open");
13034        }
13035    });
13036}
13037
13038fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13039    let position = || lsp::Position {
13040        line: params.text_document_position.position.line,
13041        character: params.text_document_position.position.character,
13042    };
13043    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13044        range: lsp::Range {
13045            start: position(),
13046            end: position(),
13047        },
13048        new_text: text.to_string(),
13049    }))
13050}
13051
13052#[gpui::test]
13053async fn test_multiline_completion(cx: &mut TestAppContext) {
13054    init_test(cx, |_| {});
13055
13056    let fs = FakeFs::new(cx.executor());
13057    fs.insert_tree(
13058        path!("/a"),
13059        json!({
13060            "main.ts": "a",
13061        }),
13062    )
13063    .await;
13064
13065    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13066    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13067    let typescript_language = Arc::new(Language::new(
13068        LanguageConfig {
13069            name: "TypeScript".into(),
13070            matcher: LanguageMatcher {
13071                path_suffixes: vec!["ts".to_string()],
13072                ..LanguageMatcher::default()
13073            },
13074            line_comments: vec!["// ".into()],
13075            ..LanguageConfig::default()
13076        },
13077        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13078    ));
13079    language_registry.add(typescript_language.clone());
13080    let mut fake_servers = language_registry.register_fake_lsp(
13081        "TypeScript",
13082        FakeLspAdapter {
13083            capabilities: lsp::ServerCapabilities {
13084                completion_provider: Some(lsp::CompletionOptions {
13085                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13086                    ..lsp::CompletionOptions::default()
13087                }),
13088                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13089                ..lsp::ServerCapabilities::default()
13090            },
13091            // Emulate vtsls label generation
13092            label_for_completion: Some(Box::new(|item, _| {
13093                let text = if let Some(description) = item
13094                    .label_details
13095                    .as_ref()
13096                    .and_then(|label_details| label_details.description.as_ref())
13097                {
13098                    format!("{} {}", item.label, description)
13099                } else if let Some(detail) = &item.detail {
13100                    format!("{} {}", item.label, detail)
13101                } else {
13102                    item.label.clone()
13103                };
13104                let len = text.len();
13105                Some(language::CodeLabel {
13106                    text,
13107                    runs: Vec::new(),
13108                    filter_range: 0..len,
13109                })
13110            })),
13111            ..FakeLspAdapter::default()
13112        },
13113    );
13114    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13115    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13116    let worktree_id = workspace
13117        .update(cx, |workspace, _window, cx| {
13118            workspace.project().update(cx, |project, cx| {
13119                project.worktrees(cx).next().unwrap().read(cx).id()
13120            })
13121        })
13122        .unwrap();
13123    let _buffer = project
13124        .update(cx, |project, cx| {
13125            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13126        })
13127        .await
13128        .unwrap();
13129    let editor = workspace
13130        .update(cx, |workspace, window, cx| {
13131            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13132        })
13133        .unwrap()
13134        .await
13135        .unwrap()
13136        .downcast::<Editor>()
13137        .unwrap();
13138    let fake_server = fake_servers.next().await.unwrap();
13139
13140    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13141    let multiline_label_2 = "a\nb\nc\n";
13142    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13143    let multiline_description = "d\ne\nf\n";
13144    let multiline_detail_2 = "g\nh\ni\n";
13145
13146    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13147        move |params, _| async move {
13148            Ok(Some(lsp::CompletionResponse::Array(vec![
13149                lsp::CompletionItem {
13150                    label: multiline_label.to_string(),
13151                    text_edit: gen_text_edit(&params, "new_text_1"),
13152                    ..lsp::CompletionItem::default()
13153                },
13154                lsp::CompletionItem {
13155                    label: "single line label 1".to_string(),
13156                    detail: Some(multiline_detail.to_string()),
13157                    text_edit: gen_text_edit(&params, "new_text_2"),
13158                    ..lsp::CompletionItem::default()
13159                },
13160                lsp::CompletionItem {
13161                    label: "single line label 2".to_string(),
13162                    label_details: Some(lsp::CompletionItemLabelDetails {
13163                        description: Some(multiline_description.to_string()),
13164                        detail: None,
13165                    }),
13166                    text_edit: gen_text_edit(&params, "new_text_2"),
13167                    ..lsp::CompletionItem::default()
13168                },
13169                lsp::CompletionItem {
13170                    label: multiline_label_2.to_string(),
13171                    detail: Some(multiline_detail_2.to_string()),
13172                    text_edit: gen_text_edit(&params, "new_text_3"),
13173                    ..lsp::CompletionItem::default()
13174                },
13175                lsp::CompletionItem {
13176                    label: "Label with many     spaces and \t but without newlines".to_string(),
13177                    detail: Some(
13178                        "Details with many     spaces and \t but without newlines".to_string(),
13179                    ),
13180                    text_edit: gen_text_edit(&params, "new_text_4"),
13181                    ..lsp::CompletionItem::default()
13182                },
13183            ])))
13184        },
13185    );
13186
13187    editor.update_in(cx, |editor, window, cx| {
13188        cx.focus_self(window);
13189        editor.move_to_end(&MoveToEnd, window, cx);
13190        editor.handle_input(".", window, cx);
13191    });
13192    cx.run_until_parked();
13193    completion_handle.next().await.unwrap();
13194
13195    editor.update(cx, |editor, _| {
13196        assert!(editor.context_menu_visible());
13197        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13198        {
13199            let completion_labels = menu
13200                .completions
13201                .borrow()
13202                .iter()
13203                .map(|c| c.label.text.clone())
13204                .collect::<Vec<_>>();
13205            assert_eq!(
13206                completion_labels,
13207                &[
13208                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13209                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13210                    "single line label 2 d e f ",
13211                    "a b c g h i ",
13212                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13213                ],
13214                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13215            );
13216
13217            for completion in menu
13218                .completions
13219                .borrow()
13220                .iter() {
13221                    assert_eq!(
13222                        completion.label.filter_range,
13223                        0..completion.label.text.len(),
13224                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13225                    );
13226                }
13227        } else {
13228            panic!("expected completion menu to be open");
13229        }
13230    });
13231}
13232
13233#[gpui::test]
13234async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13235    init_test(cx, |_| {});
13236    let mut cx = EditorLspTestContext::new_rust(
13237        lsp::ServerCapabilities {
13238            completion_provider: Some(lsp::CompletionOptions {
13239                trigger_characters: Some(vec![".".to_string()]),
13240                ..Default::default()
13241            }),
13242            ..Default::default()
13243        },
13244        cx,
13245    )
13246    .await;
13247    cx.lsp
13248        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13249            Ok(Some(lsp::CompletionResponse::Array(vec![
13250                lsp::CompletionItem {
13251                    label: "first".into(),
13252                    ..Default::default()
13253                },
13254                lsp::CompletionItem {
13255                    label: "last".into(),
13256                    ..Default::default()
13257                },
13258            ])))
13259        });
13260    cx.set_state("variableˇ");
13261    cx.simulate_keystroke(".");
13262    cx.executor().run_until_parked();
13263
13264    cx.update_editor(|editor, _, _| {
13265        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13266        {
13267            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13268        } else {
13269            panic!("expected completion menu to be open");
13270        }
13271    });
13272
13273    cx.update_editor(|editor, window, cx| {
13274        editor.move_page_down(&MovePageDown::default(), window, cx);
13275        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13276        {
13277            assert!(
13278                menu.selected_item == 1,
13279                "expected PageDown to select the last item from the context menu"
13280            );
13281        } else {
13282            panic!("expected completion menu to stay open after PageDown");
13283        }
13284    });
13285
13286    cx.update_editor(|editor, window, cx| {
13287        editor.move_page_up(&MovePageUp::default(), window, cx);
13288        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13289        {
13290            assert!(
13291                menu.selected_item == 0,
13292                "expected PageUp to select the first item from the context menu"
13293            );
13294        } else {
13295            panic!("expected completion menu to stay open after PageUp");
13296        }
13297    });
13298}
13299
13300#[gpui::test]
13301async fn test_as_is_completions(cx: &mut TestAppContext) {
13302    init_test(cx, |_| {});
13303    let mut cx = EditorLspTestContext::new_rust(
13304        lsp::ServerCapabilities {
13305            completion_provider: Some(lsp::CompletionOptions {
13306                ..Default::default()
13307            }),
13308            ..Default::default()
13309        },
13310        cx,
13311    )
13312    .await;
13313    cx.lsp
13314        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13315            Ok(Some(lsp::CompletionResponse::Array(vec![
13316                lsp::CompletionItem {
13317                    label: "unsafe".into(),
13318                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13319                        range: lsp::Range {
13320                            start: lsp::Position {
13321                                line: 1,
13322                                character: 2,
13323                            },
13324                            end: lsp::Position {
13325                                line: 1,
13326                                character: 3,
13327                            },
13328                        },
13329                        new_text: "unsafe".to_string(),
13330                    })),
13331                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13332                    ..Default::default()
13333                },
13334            ])))
13335        });
13336    cx.set_state("fn a() {}\n");
13337    cx.executor().run_until_parked();
13338    cx.update_editor(|editor, window, cx| {
13339        editor.show_completions(
13340            &ShowCompletions {
13341                trigger: Some("\n".into()),
13342            },
13343            window,
13344            cx,
13345        );
13346    });
13347    cx.executor().run_until_parked();
13348
13349    cx.update_editor(|editor, window, cx| {
13350        editor.confirm_completion(&Default::default(), window, cx)
13351    });
13352    cx.executor().run_until_parked();
13353    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13354}
13355
13356#[gpui::test]
13357async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13358    init_test(cx, |_| {});
13359
13360    let mut cx = EditorLspTestContext::new_rust(
13361        lsp::ServerCapabilities {
13362            completion_provider: Some(lsp::CompletionOptions {
13363                trigger_characters: Some(vec![".".to_string()]),
13364                resolve_provider: Some(true),
13365                ..Default::default()
13366            }),
13367            ..Default::default()
13368        },
13369        cx,
13370    )
13371    .await;
13372
13373    cx.set_state("fn main() { let a = 2ˇ; }");
13374    cx.simulate_keystroke(".");
13375    let completion_item = lsp::CompletionItem {
13376        label: "Some".into(),
13377        kind: Some(lsp::CompletionItemKind::SNIPPET),
13378        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13379        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13380            kind: lsp::MarkupKind::Markdown,
13381            value: "```rust\nSome(2)\n```".to_string(),
13382        })),
13383        deprecated: Some(false),
13384        sort_text: Some("Some".to_string()),
13385        filter_text: Some("Some".to_string()),
13386        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13387        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13388            range: lsp::Range {
13389                start: lsp::Position {
13390                    line: 0,
13391                    character: 22,
13392                },
13393                end: lsp::Position {
13394                    line: 0,
13395                    character: 22,
13396                },
13397            },
13398            new_text: "Some(2)".to_string(),
13399        })),
13400        additional_text_edits: Some(vec![lsp::TextEdit {
13401            range: lsp::Range {
13402                start: lsp::Position {
13403                    line: 0,
13404                    character: 20,
13405                },
13406                end: lsp::Position {
13407                    line: 0,
13408                    character: 22,
13409                },
13410            },
13411            new_text: "".to_string(),
13412        }]),
13413        ..Default::default()
13414    };
13415
13416    let closure_completion_item = completion_item.clone();
13417    let counter = Arc::new(AtomicUsize::new(0));
13418    let counter_clone = counter.clone();
13419    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13420        let task_completion_item = closure_completion_item.clone();
13421        counter_clone.fetch_add(1, atomic::Ordering::Release);
13422        async move {
13423            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13424                is_incomplete: true,
13425                item_defaults: None,
13426                items: vec![task_completion_item],
13427            })))
13428        }
13429    });
13430
13431    cx.condition(|editor, _| editor.context_menu_visible())
13432        .await;
13433    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13434    assert!(request.next().await.is_some());
13435    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13436
13437    cx.simulate_keystrokes("S o m");
13438    cx.condition(|editor, _| editor.context_menu_visible())
13439        .await;
13440    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13441    assert!(request.next().await.is_some());
13442    assert!(request.next().await.is_some());
13443    assert!(request.next().await.is_some());
13444    request.close();
13445    assert!(request.next().await.is_none());
13446    assert_eq!(
13447        counter.load(atomic::Ordering::Acquire),
13448        4,
13449        "With the completions menu open, only one LSP request should happen per input"
13450    );
13451}
13452
13453#[gpui::test]
13454async fn test_toggle_comment(cx: &mut TestAppContext) {
13455    init_test(cx, |_| {});
13456    let mut cx = EditorTestContext::new(cx).await;
13457    let language = Arc::new(Language::new(
13458        LanguageConfig {
13459            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13460            ..Default::default()
13461        },
13462        Some(tree_sitter_rust::LANGUAGE.into()),
13463    ));
13464    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13465
13466    // If multiple selections intersect a line, the line is only toggled once.
13467    cx.set_state(indoc! {"
13468        fn a() {
13469            «//b();
13470            ˇ»// «c();
13471            //ˇ»  d();
13472        }
13473    "});
13474
13475    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13476
13477    cx.assert_editor_state(indoc! {"
13478        fn a() {
13479            «b();
13480            c();
13481            ˇ» d();
13482        }
13483    "});
13484
13485    // The comment prefix is inserted at the same column for every line in a
13486    // selection.
13487    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13488
13489    cx.assert_editor_state(indoc! {"
13490        fn a() {
13491            // «b();
13492            // c();
13493            ˇ»//  d();
13494        }
13495    "});
13496
13497    // If a selection ends at the beginning of a line, that line is not toggled.
13498    cx.set_selections_state(indoc! {"
13499        fn a() {
13500            // b();
13501            «// c();
13502        ˇ»    //  d();
13503        }
13504    "});
13505
13506    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13507
13508    cx.assert_editor_state(indoc! {"
13509        fn a() {
13510            // b();
13511            «c();
13512        ˇ»    //  d();
13513        }
13514    "});
13515
13516    // If a selection span a single line and is empty, the line is toggled.
13517    cx.set_state(indoc! {"
13518        fn a() {
13519            a();
13520            b();
13521        ˇ
13522        }
13523    "});
13524
13525    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13526
13527    cx.assert_editor_state(indoc! {"
13528        fn a() {
13529            a();
13530            b();
13531        //•ˇ
13532        }
13533    "});
13534
13535    // If a selection span multiple lines, empty lines are not toggled.
13536    cx.set_state(indoc! {"
13537        fn a() {
13538            «a();
13539
13540            c();ˇ»
13541        }
13542    "});
13543
13544    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13545
13546    cx.assert_editor_state(indoc! {"
13547        fn a() {
13548            // «a();
13549
13550            // c();ˇ»
13551        }
13552    "});
13553
13554    // If a selection includes multiple comment prefixes, all lines are uncommented.
13555    cx.set_state(indoc! {"
13556        fn a() {
13557            «// a();
13558            /// b();
13559            //! c();ˇ»
13560        }
13561    "});
13562
13563    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13564
13565    cx.assert_editor_state(indoc! {"
13566        fn a() {
13567            «a();
13568            b();
13569            c();ˇ»
13570        }
13571    "});
13572}
13573
13574#[gpui::test]
13575async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13576    init_test(cx, |_| {});
13577    let mut cx = EditorTestContext::new(cx).await;
13578    let language = Arc::new(Language::new(
13579        LanguageConfig {
13580            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13581            ..Default::default()
13582        },
13583        Some(tree_sitter_rust::LANGUAGE.into()),
13584    ));
13585    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13586
13587    let toggle_comments = &ToggleComments {
13588        advance_downwards: false,
13589        ignore_indent: true,
13590    };
13591
13592    // If multiple selections intersect a line, the line is only toggled once.
13593    cx.set_state(indoc! {"
13594        fn a() {
13595        //    «b();
13596        //    c();
13597        //    ˇ» d();
13598        }
13599    "});
13600
13601    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13602
13603    cx.assert_editor_state(indoc! {"
13604        fn a() {
13605            «b();
13606            c();
13607            ˇ» d();
13608        }
13609    "});
13610
13611    // The comment prefix is inserted at the beginning of each line
13612    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13613
13614    cx.assert_editor_state(indoc! {"
13615        fn a() {
13616        //    «b();
13617        //    c();
13618        //    ˇ» d();
13619        }
13620    "});
13621
13622    // If a selection ends at the beginning of a line, that line is not toggled.
13623    cx.set_selections_state(indoc! {"
13624        fn a() {
13625        //    b();
13626        //    «c();
13627        ˇ»//     d();
13628        }
13629    "});
13630
13631    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13632
13633    cx.assert_editor_state(indoc! {"
13634        fn a() {
13635        //    b();
13636            «c();
13637        ˇ»//     d();
13638        }
13639    "});
13640
13641    // If a selection span a single line and is empty, the line is toggled.
13642    cx.set_state(indoc! {"
13643        fn a() {
13644            a();
13645            b();
13646        ˇ
13647        }
13648    "});
13649
13650    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13651
13652    cx.assert_editor_state(indoc! {"
13653        fn a() {
13654            a();
13655            b();
13656        //ˇ
13657        }
13658    "});
13659
13660    // If a selection span multiple lines, empty lines are not toggled.
13661    cx.set_state(indoc! {"
13662        fn a() {
13663            «a();
13664
13665            c();ˇ»
13666        }
13667    "});
13668
13669    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13670
13671    cx.assert_editor_state(indoc! {"
13672        fn a() {
13673        //    «a();
13674
13675        //    c();ˇ»
13676        }
13677    "});
13678
13679    // If a selection includes multiple comment prefixes, all lines are uncommented.
13680    cx.set_state(indoc! {"
13681        fn a() {
13682        //    «a();
13683        ///    b();
13684        //!    c();ˇ»
13685        }
13686    "});
13687
13688    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13689
13690    cx.assert_editor_state(indoc! {"
13691        fn a() {
13692            «a();
13693            b();
13694            c();ˇ»
13695        }
13696    "});
13697}
13698
13699#[gpui::test]
13700async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13701    init_test(cx, |_| {});
13702
13703    let language = Arc::new(Language::new(
13704        LanguageConfig {
13705            line_comments: vec!["// ".into()],
13706            ..Default::default()
13707        },
13708        Some(tree_sitter_rust::LANGUAGE.into()),
13709    ));
13710
13711    let mut cx = EditorTestContext::new(cx).await;
13712
13713    cx.language_registry().add(language.clone());
13714    cx.update_buffer(|buffer, cx| {
13715        buffer.set_language(Some(language), cx);
13716    });
13717
13718    let toggle_comments = &ToggleComments {
13719        advance_downwards: true,
13720        ignore_indent: false,
13721    };
13722
13723    // Single cursor on one line -> advance
13724    // Cursor moves horizontally 3 characters as well on non-blank line
13725    cx.set_state(indoc!(
13726        "fn a() {
13727             ˇdog();
13728             cat();
13729        }"
13730    ));
13731    cx.update_editor(|editor, window, cx| {
13732        editor.toggle_comments(toggle_comments, window, cx);
13733    });
13734    cx.assert_editor_state(indoc!(
13735        "fn a() {
13736             // dog();
13737             catˇ();
13738        }"
13739    ));
13740
13741    // Single selection on one line -> don't advance
13742    cx.set_state(indoc!(
13743        "fn a() {
13744             «dog()ˇ»;
13745             cat();
13746        }"
13747    ));
13748    cx.update_editor(|editor, window, cx| {
13749        editor.toggle_comments(toggle_comments, window, cx);
13750    });
13751    cx.assert_editor_state(indoc!(
13752        "fn a() {
13753             // «dog()ˇ»;
13754             cat();
13755        }"
13756    ));
13757
13758    // Multiple cursors on one line -> advance
13759    cx.set_state(indoc!(
13760        "fn a() {
13761             ˇdˇog();
13762             cat();
13763        }"
13764    ));
13765    cx.update_editor(|editor, window, cx| {
13766        editor.toggle_comments(toggle_comments, window, cx);
13767    });
13768    cx.assert_editor_state(indoc!(
13769        "fn a() {
13770             // dog();
13771             catˇ(ˇ);
13772        }"
13773    ));
13774
13775    // Multiple cursors on one line, with selection -> don't advance
13776    cx.set_state(indoc!(
13777        "fn a() {
13778             ˇdˇog«()ˇ»;
13779             cat();
13780        }"
13781    ));
13782    cx.update_editor(|editor, window, cx| {
13783        editor.toggle_comments(toggle_comments, window, cx);
13784    });
13785    cx.assert_editor_state(indoc!(
13786        "fn a() {
13787             // ˇdˇog«()ˇ»;
13788             cat();
13789        }"
13790    ));
13791
13792    // Single cursor on one line -> advance
13793    // Cursor moves to column 0 on blank line
13794    cx.set_state(indoc!(
13795        "fn a() {
13796             ˇdog();
13797
13798             cat();
13799        }"
13800    ));
13801    cx.update_editor(|editor, window, cx| {
13802        editor.toggle_comments(toggle_comments, window, cx);
13803    });
13804    cx.assert_editor_state(indoc!(
13805        "fn a() {
13806             // dog();
13807        ˇ
13808             cat();
13809        }"
13810    ));
13811
13812    // Single cursor on one line -> advance
13813    // Cursor starts and ends at column 0
13814    cx.set_state(indoc!(
13815        "fn a() {
13816         ˇ    dog();
13817             cat();
13818        }"
13819    ));
13820    cx.update_editor(|editor, window, cx| {
13821        editor.toggle_comments(toggle_comments, window, cx);
13822    });
13823    cx.assert_editor_state(indoc!(
13824        "fn a() {
13825             // dog();
13826         ˇ    cat();
13827        }"
13828    ));
13829}
13830
13831#[gpui::test]
13832async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13833    init_test(cx, |_| {});
13834
13835    let mut cx = EditorTestContext::new(cx).await;
13836
13837    let html_language = Arc::new(
13838        Language::new(
13839            LanguageConfig {
13840                name: "HTML".into(),
13841                block_comment: Some(BlockCommentConfig {
13842                    start: "<!-- ".into(),
13843                    prefix: "".into(),
13844                    end: " -->".into(),
13845                    tab_size: 0,
13846                }),
13847                ..Default::default()
13848            },
13849            Some(tree_sitter_html::LANGUAGE.into()),
13850        )
13851        .with_injection_query(
13852            r#"
13853            (script_element
13854                (raw_text) @injection.content
13855                (#set! injection.language "javascript"))
13856            "#,
13857        )
13858        .unwrap(),
13859    );
13860
13861    let javascript_language = Arc::new(Language::new(
13862        LanguageConfig {
13863            name: "JavaScript".into(),
13864            line_comments: vec!["// ".into()],
13865            ..Default::default()
13866        },
13867        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13868    ));
13869
13870    cx.language_registry().add(html_language.clone());
13871    cx.language_registry().add(javascript_language.clone());
13872    cx.update_buffer(|buffer, cx| {
13873        buffer.set_language(Some(html_language), cx);
13874    });
13875
13876    // Toggle comments for empty selections
13877    cx.set_state(
13878        &r#"
13879            <p>A</p>ˇ
13880            <p>B</p>ˇ
13881            <p>C</p>ˇ
13882        "#
13883        .unindent(),
13884    );
13885    cx.update_editor(|editor, window, cx| {
13886        editor.toggle_comments(&ToggleComments::default(), window, cx)
13887    });
13888    cx.assert_editor_state(
13889        &r#"
13890            <!-- <p>A</p>ˇ -->
13891            <!-- <p>B</p>ˇ -->
13892            <!-- <p>C</p>ˇ -->
13893        "#
13894        .unindent(),
13895    );
13896    cx.update_editor(|editor, window, cx| {
13897        editor.toggle_comments(&ToggleComments::default(), window, cx)
13898    });
13899    cx.assert_editor_state(
13900        &r#"
13901            <p>A</p>ˇ
13902            <p>B</p>ˇ
13903            <p>C</p>ˇ
13904        "#
13905        .unindent(),
13906    );
13907
13908    // Toggle comments for mixture of empty and non-empty selections, where
13909    // multiple selections occupy a given line.
13910    cx.set_state(
13911        &r#"
13912            <p>A«</p>
13913            <p>ˇ»B</p>ˇ
13914            <p>C«</p>
13915            <p>ˇ»D</p>ˇ
13916        "#
13917        .unindent(),
13918    );
13919
13920    cx.update_editor(|editor, window, cx| {
13921        editor.toggle_comments(&ToggleComments::default(), window, cx)
13922    });
13923    cx.assert_editor_state(
13924        &r#"
13925            <!-- <p>A«</p>
13926            <p>ˇ»B</p>ˇ -->
13927            <!-- <p>C«</p>
13928            <p>ˇ»D</p>ˇ -->
13929        "#
13930        .unindent(),
13931    );
13932    cx.update_editor(|editor, window, cx| {
13933        editor.toggle_comments(&ToggleComments::default(), window, cx)
13934    });
13935    cx.assert_editor_state(
13936        &r#"
13937            <p>A«</p>
13938            <p>ˇ»B</p>ˇ
13939            <p>C«</p>
13940            <p>ˇ»D</p>ˇ
13941        "#
13942        .unindent(),
13943    );
13944
13945    // Toggle comments when different languages are active for different
13946    // selections.
13947    cx.set_state(
13948        &r#"
13949            ˇ<script>
13950                ˇvar x = new Y();
13951            ˇ</script>
13952        "#
13953        .unindent(),
13954    );
13955    cx.executor().run_until_parked();
13956    cx.update_editor(|editor, window, cx| {
13957        editor.toggle_comments(&ToggleComments::default(), window, cx)
13958    });
13959    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13960    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13961    cx.assert_editor_state(
13962        &r#"
13963            <!-- ˇ<script> -->
13964                // ˇvar x = new Y();
13965            <!-- ˇ</script> -->
13966        "#
13967        .unindent(),
13968    );
13969}
13970
13971#[gpui::test]
13972fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13973    init_test(cx, |_| {});
13974
13975    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13976    let multibuffer = cx.new(|cx| {
13977        let mut multibuffer = MultiBuffer::new(ReadWrite);
13978        multibuffer.push_excerpts(
13979            buffer.clone(),
13980            [
13981                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13982                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13983            ],
13984            cx,
13985        );
13986        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13987        multibuffer
13988    });
13989
13990    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13991    editor.update_in(cx, |editor, window, cx| {
13992        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13994            s.select_ranges([
13995                Point::new(0, 0)..Point::new(0, 0),
13996                Point::new(1, 0)..Point::new(1, 0),
13997            ])
13998        });
13999
14000        editor.handle_input("X", window, cx);
14001        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14002        assert_eq!(
14003            editor.selections.ranges(cx),
14004            [
14005                Point::new(0, 1)..Point::new(0, 1),
14006                Point::new(1, 1)..Point::new(1, 1),
14007            ]
14008        );
14009
14010        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14012            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14013        });
14014        editor.backspace(&Default::default(), window, cx);
14015        assert_eq!(editor.text(cx), "Xa\nbbb");
14016        assert_eq!(
14017            editor.selections.ranges(cx),
14018            [Point::new(1, 0)..Point::new(1, 0)]
14019        );
14020
14021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14022            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14023        });
14024        editor.backspace(&Default::default(), window, cx);
14025        assert_eq!(editor.text(cx), "X\nbb");
14026        assert_eq!(
14027            editor.selections.ranges(cx),
14028            [Point::new(0, 1)..Point::new(0, 1)]
14029        );
14030    });
14031}
14032
14033#[gpui::test]
14034fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14035    init_test(cx, |_| {});
14036
14037    let markers = vec![('[', ']').into(), ('(', ')').into()];
14038    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14039        indoc! {"
14040            [aaaa
14041            (bbbb]
14042            cccc)",
14043        },
14044        markers.clone(),
14045    );
14046    let excerpt_ranges = markers.into_iter().map(|marker| {
14047        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14048        ExcerptRange::new(context.clone())
14049    });
14050    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14051    let multibuffer = cx.new(|cx| {
14052        let mut multibuffer = MultiBuffer::new(ReadWrite);
14053        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14054        multibuffer
14055    });
14056
14057    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14058    editor.update_in(cx, |editor, window, cx| {
14059        let (expected_text, selection_ranges) = marked_text_ranges(
14060            indoc! {"
14061                aaaa
14062                bˇbbb
14063                bˇbbˇb
14064                cccc"
14065            },
14066            true,
14067        );
14068        assert_eq!(editor.text(cx), expected_text);
14069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14070            s.select_ranges(selection_ranges)
14071        });
14072
14073        editor.handle_input("X", window, cx);
14074
14075        let (expected_text, expected_selections) = marked_text_ranges(
14076            indoc! {"
14077                aaaa
14078                bXˇbbXb
14079                bXˇbbXˇb
14080                cccc"
14081            },
14082            false,
14083        );
14084        assert_eq!(editor.text(cx), expected_text);
14085        assert_eq!(editor.selections.ranges(cx), expected_selections);
14086
14087        editor.newline(&Newline, window, cx);
14088        let (expected_text, expected_selections) = marked_text_ranges(
14089            indoc! {"
14090                aaaa
14091                bX
14092                ˇbbX
14093                b
14094                bX
14095                ˇbbX
14096                ˇb
14097                cccc"
14098            },
14099            false,
14100        );
14101        assert_eq!(editor.text(cx), expected_text);
14102        assert_eq!(editor.selections.ranges(cx), expected_selections);
14103    });
14104}
14105
14106#[gpui::test]
14107fn test_refresh_selections(cx: &mut TestAppContext) {
14108    init_test(cx, |_| {});
14109
14110    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14111    let mut excerpt1_id = None;
14112    let multibuffer = cx.new(|cx| {
14113        let mut multibuffer = MultiBuffer::new(ReadWrite);
14114        excerpt1_id = multibuffer
14115            .push_excerpts(
14116                buffer.clone(),
14117                [
14118                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14119                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14120                ],
14121                cx,
14122            )
14123            .into_iter()
14124            .next();
14125        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14126        multibuffer
14127    });
14128
14129    let editor = cx.add_window(|window, cx| {
14130        let mut editor = build_editor(multibuffer.clone(), window, cx);
14131        let snapshot = editor.snapshot(window, cx);
14132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14133            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14134        });
14135        editor.begin_selection(
14136            Point::new(2, 1).to_display_point(&snapshot),
14137            true,
14138            1,
14139            window,
14140            cx,
14141        );
14142        assert_eq!(
14143            editor.selections.ranges(cx),
14144            [
14145                Point::new(1, 3)..Point::new(1, 3),
14146                Point::new(2, 1)..Point::new(2, 1),
14147            ]
14148        );
14149        editor
14150    });
14151
14152    // Refreshing selections is a no-op when excerpts haven't changed.
14153    _ = editor.update(cx, |editor, window, cx| {
14154        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14155        assert_eq!(
14156            editor.selections.ranges(cx),
14157            [
14158                Point::new(1, 3)..Point::new(1, 3),
14159                Point::new(2, 1)..Point::new(2, 1),
14160            ]
14161        );
14162    });
14163
14164    multibuffer.update(cx, |multibuffer, cx| {
14165        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14166    });
14167    _ = editor.update(cx, |editor, window, cx| {
14168        // Removing an excerpt causes the first selection to become degenerate.
14169        assert_eq!(
14170            editor.selections.ranges(cx),
14171            [
14172                Point::new(0, 0)..Point::new(0, 0),
14173                Point::new(0, 1)..Point::new(0, 1)
14174            ]
14175        );
14176
14177        // Refreshing selections will relocate the first selection to the original buffer
14178        // location.
14179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14180        assert_eq!(
14181            editor.selections.ranges(cx),
14182            [
14183                Point::new(0, 1)..Point::new(0, 1),
14184                Point::new(0, 3)..Point::new(0, 3)
14185            ]
14186        );
14187        assert!(editor.selections.pending_anchor().is_some());
14188    });
14189}
14190
14191#[gpui::test]
14192fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14193    init_test(cx, |_| {});
14194
14195    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14196    let mut excerpt1_id = None;
14197    let multibuffer = cx.new(|cx| {
14198        let mut multibuffer = MultiBuffer::new(ReadWrite);
14199        excerpt1_id = multibuffer
14200            .push_excerpts(
14201                buffer.clone(),
14202                [
14203                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14204                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14205                ],
14206                cx,
14207            )
14208            .into_iter()
14209            .next();
14210        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14211        multibuffer
14212    });
14213
14214    let editor = cx.add_window(|window, cx| {
14215        let mut editor = build_editor(multibuffer.clone(), window, cx);
14216        let snapshot = editor.snapshot(window, cx);
14217        editor.begin_selection(
14218            Point::new(1, 3).to_display_point(&snapshot),
14219            false,
14220            1,
14221            window,
14222            cx,
14223        );
14224        assert_eq!(
14225            editor.selections.ranges(cx),
14226            [Point::new(1, 3)..Point::new(1, 3)]
14227        );
14228        editor
14229    });
14230
14231    multibuffer.update(cx, |multibuffer, cx| {
14232        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14233    });
14234    _ = editor.update(cx, |editor, window, cx| {
14235        assert_eq!(
14236            editor.selections.ranges(cx),
14237            [Point::new(0, 0)..Point::new(0, 0)]
14238        );
14239
14240        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14242        assert_eq!(
14243            editor.selections.ranges(cx),
14244            [Point::new(0, 3)..Point::new(0, 3)]
14245        );
14246        assert!(editor.selections.pending_anchor().is_some());
14247    });
14248}
14249
14250#[gpui::test]
14251async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14252    init_test(cx, |_| {});
14253
14254    let language = Arc::new(
14255        Language::new(
14256            LanguageConfig {
14257                brackets: BracketPairConfig {
14258                    pairs: vec![
14259                        BracketPair {
14260                            start: "{".to_string(),
14261                            end: "}".to_string(),
14262                            close: true,
14263                            surround: true,
14264                            newline: true,
14265                        },
14266                        BracketPair {
14267                            start: "/* ".to_string(),
14268                            end: " */".to_string(),
14269                            close: true,
14270                            surround: true,
14271                            newline: true,
14272                        },
14273                    ],
14274                    ..Default::default()
14275                },
14276                ..Default::default()
14277            },
14278            Some(tree_sitter_rust::LANGUAGE.into()),
14279        )
14280        .with_indents_query("")
14281        .unwrap(),
14282    );
14283
14284    let text = concat!(
14285        "{   }\n",     //
14286        "  x\n",       //
14287        "  /*   */\n", //
14288        "x\n",         //
14289        "{{} }\n",     //
14290    );
14291
14292    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14293    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14294    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14295    editor
14296        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14297        .await;
14298
14299    editor.update_in(cx, |editor, window, cx| {
14300        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14301            s.select_display_ranges([
14302                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14303                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14304                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14305            ])
14306        });
14307        editor.newline(&Newline, window, cx);
14308
14309        assert_eq!(
14310            editor.buffer().read(cx).read(cx).text(),
14311            concat!(
14312                "{ \n",    // Suppress rustfmt
14313                "\n",      //
14314                "}\n",     //
14315                "  x\n",   //
14316                "  /* \n", //
14317                "  \n",    //
14318                "  */\n",  //
14319                "x\n",     //
14320                "{{} \n",  //
14321                "}\n",     //
14322            )
14323        );
14324    });
14325}
14326
14327#[gpui::test]
14328fn test_highlighted_ranges(cx: &mut TestAppContext) {
14329    init_test(cx, |_| {});
14330
14331    let editor = cx.add_window(|window, cx| {
14332        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14333        build_editor(buffer.clone(), window, cx)
14334    });
14335
14336    _ = editor.update(cx, |editor, window, cx| {
14337        struct Type1;
14338        struct Type2;
14339
14340        let buffer = editor.buffer.read(cx).snapshot(cx);
14341
14342        let anchor_range =
14343            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14344
14345        editor.highlight_background::<Type1>(
14346            &[
14347                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14348                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14349                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14350                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14351            ],
14352            |_| Hsla::red(),
14353            cx,
14354        );
14355        editor.highlight_background::<Type2>(
14356            &[
14357                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14358                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14359                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14360                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14361            ],
14362            |_| Hsla::green(),
14363            cx,
14364        );
14365
14366        let snapshot = editor.snapshot(window, cx);
14367        let mut highlighted_ranges = editor.background_highlights_in_range(
14368            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14369            &snapshot,
14370            cx.theme(),
14371        );
14372        // Enforce a consistent ordering based on color without relying on the ordering of the
14373        // highlight's `TypeId` which is non-executor.
14374        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14375        assert_eq!(
14376            highlighted_ranges,
14377            &[
14378                (
14379                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14380                    Hsla::red(),
14381                ),
14382                (
14383                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14384                    Hsla::red(),
14385                ),
14386                (
14387                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14388                    Hsla::green(),
14389                ),
14390                (
14391                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14392                    Hsla::green(),
14393                ),
14394            ]
14395        );
14396        assert_eq!(
14397            editor.background_highlights_in_range(
14398                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14399                &snapshot,
14400                cx.theme(),
14401            ),
14402            &[(
14403                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14404                Hsla::red(),
14405            )]
14406        );
14407    });
14408}
14409
14410#[gpui::test]
14411async fn test_following(cx: &mut TestAppContext) {
14412    init_test(cx, |_| {});
14413
14414    let fs = FakeFs::new(cx.executor());
14415    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14416
14417    let buffer = project.update(cx, |project, cx| {
14418        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14419        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14420    });
14421    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14422    let follower = cx.update(|cx| {
14423        cx.open_window(
14424            WindowOptions {
14425                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14426                    gpui::Point::new(px(0.), px(0.)),
14427                    gpui::Point::new(px(10.), px(80.)),
14428                ))),
14429                ..Default::default()
14430            },
14431            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14432        )
14433        .unwrap()
14434    });
14435
14436    let is_still_following = Rc::new(RefCell::new(true));
14437    let follower_edit_event_count = Rc::new(RefCell::new(0));
14438    let pending_update = Rc::new(RefCell::new(None));
14439    let leader_entity = leader.root(cx).unwrap();
14440    let follower_entity = follower.root(cx).unwrap();
14441    _ = follower.update(cx, {
14442        let update = pending_update.clone();
14443        let is_still_following = is_still_following.clone();
14444        let follower_edit_event_count = follower_edit_event_count.clone();
14445        |_, window, cx| {
14446            cx.subscribe_in(
14447                &leader_entity,
14448                window,
14449                move |_, leader, event, window, cx| {
14450                    leader.read(cx).add_event_to_update_proto(
14451                        event,
14452                        &mut update.borrow_mut(),
14453                        window,
14454                        cx,
14455                    );
14456                },
14457            )
14458            .detach();
14459
14460            cx.subscribe_in(
14461                &follower_entity,
14462                window,
14463                move |_, _, event: &EditorEvent, _window, _cx| {
14464                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14465                        *is_still_following.borrow_mut() = false;
14466                    }
14467
14468                    if let EditorEvent::BufferEdited = event {
14469                        *follower_edit_event_count.borrow_mut() += 1;
14470                    }
14471                },
14472            )
14473            .detach();
14474        }
14475    });
14476
14477    // Update the selections only
14478    _ = leader.update(cx, |leader, window, cx| {
14479        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14480            s.select_ranges([1..1])
14481        });
14482    });
14483    follower
14484        .update(cx, |follower, window, cx| {
14485            follower.apply_update_proto(
14486                &project,
14487                pending_update.borrow_mut().take().unwrap(),
14488                window,
14489                cx,
14490            )
14491        })
14492        .unwrap()
14493        .await
14494        .unwrap();
14495    _ = follower.update(cx, |follower, _, cx| {
14496        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14497    });
14498    assert!(*is_still_following.borrow());
14499    assert_eq!(*follower_edit_event_count.borrow(), 0);
14500
14501    // Update the scroll position only
14502    _ = leader.update(cx, |leader, window, cx| {
14503        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14504    });
14505    follower
14506        .update(cx, |follower, window, cx| {
14507            follower.apply_update_proto(
14508                &project,
14509                pending_update.borrow_mut().take().unwrap(),
14510                window,
14511                cx,
14512            )
14513        })
14514        .unwrap()
14515        .await
14516        .unwrap();
14517    assert_eq!(
14518        follower
14519            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14520            .unwrap(),
14521        gpui::Point::new(1.5, 3.5)
14522    );
14523    assert!(*is_still_following.borrow());
14524    assert_eq!(*follower_edit_event_count.borrow(), 0);
14525
14526    // Update the selections and scroll position. The follower's scroll position is updated
14527    // via autoscroll, not via the leader's exact scroll position.
14528    _ = leader.update(cx, |leader, window, cx| {
14529        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14530            s.select_ranges([0..0])
14531        });
14532        leader.request_autoscroll(Autoscroll::newest(), cx);
14533        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14534    });
14535    follower
14536        .update(cx, |follower, window, cx| {
14537            follower.apply_update_proto(
14538                &project,
14539                pending_update.borrow_mut().take().unwrap(),
14540                window,
14541                cx,
14542            )
14543        })
14544        .unwrap()
14545        .await
14546        .unwrap();
14547    _ = follower.update(cx, |follower, _, cx| {
14548        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14549        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14550    });
14551    assert!(*is_still_following.borrow());
14552
14553    // Creating a pending selection that precedes another selection
14554    _ = leader.update(cx, |leader, window, cx| {
14555        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14556            s.select_ranges([1..1])
14557        });
14558        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14559    });
14560    follower
14561        .update(cx, |follower, window, cx| {
14562            follower.apply_update_proto(
14563                &project,
14564                pending_update.borrow_mut().take().unwrap(),
14565                window,
14566                cx,
14567            )
14568        })
14569        .unwrap()
14570        .await
14571        .unwrap();
14572    _ = follower.update(cx, |follower, _, cx| {
14573        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14574    });
14575    assert!(*is_still_following.borrow());
14576
14577    // Extend the pending selection so that it surrounds another selection
14578    _ = leader.update(cx, |leader, window, cx| {
14579        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14580    });
14581    follower
14582        .update(cx, |follower, window, cx| {
14583            follower.apply_update_proto(
14584                &project,
14585                pending_update.borrow_mut().take().unwrap(),
14586                window,
14587                cx,
14588            )
14589        })
14590        .unwrap()
14591        .await
14592        .unwrap();
14593    _ = follower.update(cx, |follower, _, cx| {
14594        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14595    });
14596
14597    // Scrolling locally breaks the follow
14598    _ = follower.update(cx, |follower, window, cx| {
14599        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14600        follower.set_scroll_anchor(
14601            ScrollAnchor {
14602                anchor: top_anchor,
14603                offset: gpui::Point::new(0.0, 0.5),
14604            },
14605            window,
14606            cx,
14607        );
14608    });
14609    assert!(!(*is_still_following.borrow()));
14610}
14611
14612#[gpui::test]
14613async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14614    init_test(cx, |_| {});
14615
14616    let fs = FakeFs::new(cx.executor());
14617    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14618    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14619    let pane = workspace
14620        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14621        .unwrap();
14622
14623    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14624
14625    let leader = pane.update_in(cx, |_, window, cx| {
14626        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14627        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14628    });
14629
14630    // Start following the editor when it has no excerpts.
14631    let mut state_message =
14632        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14633    let workspace_entity = workspace.root(cx).unwrap();
14634    let follower_1 = cx
14635        .update_window(*workspace.deref(), |_, window, cx| {
14636            Editor::from_state_proto(
14637                workspace_entity,
14638                ViewId {
14639                    creator: CollaboratorId::PeerId(PeerId::default()),
14640                    id: 0,
14641                },
14642                &mut state_message,
14643                window,
14644                cx,
14645            )
14646        })
14647        .unwrap()
14648        .unwrap()
14649        .await
14650        .unwrap();
14651
14652    let update_message = Rc::new(RefCell::new(None));
14653    follower_1.update_in(cx, {
14654        let update = update_message.clone();
14655        |_, window, cx| {
14656            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14657                leader.read(cx).add_event_to_update_proto(
14658                    event,
14659                    &mut update.borrow_mut(),
14660                    window,
14661                    cx,
14662                );
14663            })
14664            .detach();
14665        }
14666    });
14667
14668    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14669        (
14670            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14671            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14672        )
14673    });
14674
14675    // Insert some excerpts.
14676    leader.update(cx, |leader, cx| {
14677        leader.buffer.update(cx, |multibuffer, cx| {
14678            multibuffer.set_excerpts_for_path(
14679                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14680                buffer_1.clone(),
14681                vec![
14682                    Point::row_range(0..3),
14683                    Point::row_range(1..6),
14684                    Point::row_range(12..15),
14685                ],
14686                0,
14687                cx,
14688            );
14689            multibuffer.set_excerpts_for_path(
14690                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14691                buffer_2.clone(),
14692                vec![Point::row_range(0..6), Point::row_range(8..12)],
14693                0,
14694                cx,
14695            );
14696        });
14697    });
14698
14699    // Apply the update of adding the excerpts.
14700    follower_1
14701        .update_in(cx, |follower, window, cx| {
14702            follower.apply_update_proto(
14703                &project,
14704                update_message.borrow().clone().unwrap(),
14705                window,
14706                cx,
14707            )
14708        })
14709        .await
14710        .unwrap();
14711    assert_eq!(
14712        follower_1.update(cx, |editor, cx| editor.text(cx)),
14713        leader.update(cx, |editor, cx| editor.text(cx))
14714    );
14715    update_message.borrow_mut().take();
14716
14717    // Start following separately after it already has excerpts.
14718    let mut state_message =
14719        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14720    let workspace_entity = workspace.root(cx).unwrap();
14721    let follower_2 = cx
14722        .update_window(*workspace.deref(), |_, window, cx| {
14723            Editor::from_state_proto(
14724                workspace_entity,
14725                ViewId {
14726                    creator: CollaboratorId::PeerId(PeerId::default()),
14727                    id: 0,
14728                },
14729                &mut state_message,
14730                window,
14731                cx,
14732            )
14733        })
14734        .unwrap()
14735        .unwrap()
14736        .await
14737        .unwrap();
14738    assert_eq!(
14739        follower_2.update(cx, |editor, cx| editor.text(cx)),
14740        leader.update(cx, |editor, cx| editor.text(cx))
14741    );
14742
14743    // Remove some excerpts.
14744    leader.update(cx, |leader, cx| {
14745        leader.buffer.update(cx, |multibuffer, cx| {
14746            let excerpt_ids = multibuffer.excerpt_ids();
14747            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14748            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14749        });
14750    });
14751
14752    // Apply the update of removing the excerpts.
14753    follower_1
14754        .update_in(cx, |follower, window, cx| {
14755            follower.apply_update_proto(
14756                &project,
14757                update_message.borrow().clone().unwrap(),
14758                window,
14759                cx,
14760            )
14761        })
14762        .await
14763        .unwrap();
14764    follower_2
14765        .update_in(cx, |follower, window, cx| {
14766            follower.apply_update_proto(
14767                &project,
14768                update_message.borrow().clone().unwrap(),
14769                window,
14770                cx,
14771            )
14772        })
14773        .await
14774        .unwrap();
14775    update_message.borrow_mut().take();
14776    assert_eq!(
14777        follower_1.update(cx, |editor, cx| editor.text(cx)),
14778        leader.update(cx, |editor, cx| editor.text(cx))
14779    );
14780}
14781
14782#[gpui::test]
14783async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14784    init_test(cx, |_| {});
14785
14786    let mut cx = EditorTestContext::new(cx).await;
14787    let lsp_store =
14788        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14789
14790    cx.set_state(indoc! {"
14791        ˇfn func(abc def: i32) -> u32 {
14792        }
14793    "});
14794
14795    cx.update(|_, cx| {
14796        lsp_store.update(cx, |lsp_store, cx| {
14797            lsp_store
14798                .update_diagnostics(
14799                    LanguageServerId(0),
14800                    lsp::PublishDiagnosticsParams {
14801                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14802                        version: None,
14803                        diagnostics: vec![
14804                            lsp::Diagnostic {
14805                                range: lsp::Range::new(
14806                                    lsp::Position::new(0, 11),
14807                                    lsp::Position::new(0, 12),
14808                                ),
14809                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14810                                ..Default::default()
14811                            },
14812                            lsp::Diagnostic {
14813                                range: lsp::Range::new(
14814                                    lsp::Position::new(0, 12),
14815                                    lsp::Position::new(0, 15),
14816                                ),
14817                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14818                                ..Default::default()
14819                            },
14820                            lsp::Diagnostic {
14821                                range: lsp::Range::new(
14822                                    lsp::Position::new(0, 25),
14823                                    lsp::Position::new(0, 28),
14824                                ),
14825                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14826                                ..Default::default()
14827                            },
14828                        ],
14829                    },
14830                    None,
14831                    DiagnosticSourceKind::Pushed,
14832                    &[],
14833                    cx,
14834                )
14835                .unwrap()
14836        });
14837    });
14838
14839    executor.run_until_parked();
14840
14841    cx.update_editor(|editor, window, cx| {
14842        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14843    });
14844
14845    cx.assert_editor_state(indoc! {"
14846        fn func(abc def: i32) -> ˇu32 {
14847        }
14848    "});
14849
14850    cx.update_editor(|editor, window, cx| {
14851        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14852    });
14853
14854    cx.assert_editor_state(indoc! {"
14855        fn func(abc ˇdef: i32) -> u32 {
14856        }
14857    "});
14858
14859    cx.update_editor(|editor, window, cx| {
14860        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14861    });
14862
14863    cx.assert_editor_state(indoc! {"
14864        fn func(abcˇ def: i32) -> u32 {
14865        }
14866    "});
14867
14868    cx.update_editor(|editor, window, cx| {
14869        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14870    });
14871
14872    cx.assert_editor_state(indoc! {"
14873        fn func(abc def: i32) -> ˇu32 {
14874        }
14875    "});
14876}
14877
14878#[gpui::test]
14879async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14880    init_test(cx, |_| {});
14881
14882    let mut cx = EditorTestContext::new(cx).await;
14883
14884    let diff_base = r#"
14885        use some::mod;
14886
14887        const A: u32 = 42;
14888
14889        fn main() {
14890            println!("hello");
14891
14892            println!("world");
14893        }
14894        "#
14895    .unindent();
14896
14897    // Edits are modified, removed, modified, added
14898    cx.set_state(
14899        &r#"
14900        use some::modified;
14901
14902        ˇ
14903        fn main() {
14904            println!("hello there");
14905
14906            println!("around the");
14907            println!("world");
14908        }
14909        "#
14910        .unindent(),
14911    );
14912
14913    cx.set_head_text(&diff_base);
14914    executor.run_until_parked();
14915
14916    cx.update_editor(|editor, window, cx| {
14917        //Wrap around the bottom of the buffer
14918        for _ in 0..3 {
14919            editor.go_to_next_hunk(&GoToHunk, window, cx);
14920        }
14921    });
14922
14923    cx.assert_editor_state(
14924        &r#"
14925        ˇuse some::modified;
14926
14927
14928        fn main() {
14929            println!("hello there");
14930
14931            println!("around the");
14932            println!("world");
14933        }
14934        "#
14935        .unindent(),
14936    );
14937
14938    cx.update_editor(|editor, window, cx| {
14939        //Wrap around the top of the buffer
14940        for _ in 0..2 {
14941            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14942        }
14943    });
14944
14945    cx.assert_editor_state(
14946        &r#"
14947        use some::modified;
14948
14949
14950        fn main() {
14951        ˇ    println!("hello there");
14952
14953            println!("around the");
14954            println!("world");
14955        }
14956        "#
14957        .unindent(),
14958    );
14959
14960    cx.update_editor(|editor, window, cx| {
14961        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14962    });
14963
14964    cx.assert_editor_state(
14965        &r#"
14966        use some::modified;
14967
14968        ˇ
14969        fn main() {
14970            println!("hello there");
14971
14972            println!("around the");
14973            println!("world");
14974        }
14975        "#
14976        .unindent(),
14977    );
14978
14979    cx.update_editor(|editor, window, cx| {
14980        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14981    });
14982
14983    cx.assert_editor_state(
14984        &r#"
14985        ˇuse some::modified;
14986
14987
14988        fn main() {
14989            println!("hello there");
14990
14991            println!("around the");
14992            println!("world");
14993        }
14994        "#
14995        .unindent(),
14996    );
14997
14998    cx.update_editor(|editor, window, cx| {
14999        for _ in 0..2 {
15000            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15001        }
15002    });
15003
15004    cx.assert_editor_state(
15005        &r#"
15006        use some::modified;
15007
15008
15009        fn main() {
15010        ˇ    println!("hello there");
15011
15012            println!("around the");
15013            println!("world");
15014        }
15015        "#
15016        .unindent(),
15017    );
15018
15019    cx.update_editor(|editor, window, cx| {
15020        editor.fold(&Fold, window, cx);
15021    });
15022
15023    cx.update_editor(|editor, window, cx| {
15024        editor.go_to_next_hunk(&GoToHunk, window, cx);
15025    });
15026
15027    cx.assert_editor_state(
15028        &r#"
15029        ˇuse some::modified;
15030
15031
15032        fn main() {
15033            println!("hello there");
15034
15035            println!("around the");
15036            println!("world");
15037        }
15038        "#
15039        .unindent(),
15040    );
15041}
15042
15043#[test]
15044fn test_split_words() {
15045    fn split(text: &str) -> Vec<&str> {
15046        split_words(text).collect()
15047    }
15048
15049    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15050    assert_eq!(split("hello_world"), &["hello_", "world"]);
15051    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15052    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15053    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15054    assert_eq!(split("helloworld"), &["helloworld"]);
15055
15056    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15057}
15058
15059#[gpui::test]
15060async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15061    init_test(cx, |_| {});
15062
15063    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15064    let mut assert = |before, after| {
15065        let _state_context = cx.set_state(before);
15066        cx.run_until_parked();
15067        cx.update_editor(|editor, window, cx| {
15068            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15069        });
15070        cx.run_until_parked();
15071        cx.assert_editor_state(after);
15072    };
15073
15074    // Outside bracket jumps to outside of matching bracket
15075    assert("console.logˇ(var);", "console.log(var)ˇ;");
15076    assert("console.log(var)ˇ;", "console.logˇ(var);");
15077
15078    // Inside bracket jumps to inside of matching bracket
15079    assert("console.log(ˇvar);", "console.log(varˇ);");
15080    assert("console.log(varˇ);", "console.log(ˇvar);");
15081
15082    // When outside a bracket and inside, favor jumping to the inside bracket
15083    assert(
15084        "console.log('foo', [1, 2, 3]ˇ);",
15085        "console.log(ˇ'foo', [1, 2, 3]);",
15086    );
15087    assert(
15088        "console.log(ˇ'foo', [1, 2, 3]);",
15089        "console.log('foo', [1, 2, 3]ˇ);",
15090    );
15091
15092    // Bias forward if two options are equally likely
15093    assert(
15094        "let result = curried_fun()ˇ();",
15095        "let result = curried_fun()()ˇ;",
15096    );
15097
15098    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15099    assert(
15100        indoc! {"
15101            function test() {
15102                console.log('test')ˇ
15103            }"},
15104        indoc! {"
15105            function test() {
15106                console.logˇ('test')
15107            }"},
15108    );
15109}
15110
15111#[gpui::test]
15112async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15113    init_test(cx, |_| {});
15114
15115    let fs = FakeFs::new(cx.executor());
15116    fs.insert_tree(
15117        path!("/a"),
15118        json!({
15119            "main.rs": "fn main() { let a = 5; }",
15120            "other.rs": "// Test file",
15121        }),
15122    )
15123    .await;
15124    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15125
15126    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15127    language_registry.add(Arc::new(Language::new(
15128        LanguageConfig {
15129            name: "Rust".into(),
15130            matcher: LanguageMatcher {
15131                path_suffixes: vec!["rs".to_string()],
15132                ..Default::default()
15133            },
15134            brackets: BracketPairConfig {
15135                pairs: vec![BracketPair {
15136                    start: "{".to_string(),
15137                    end: "}".to_string(),
15138                    close: true,
15139                    surround: true,
15140                    newline: true,
15141                }],
15142                disabled_scopes_by_bracket_ix: Vec::new(),
15143            },
15144            ..Default::default()
15145        },
15146        Some(tree_sitter_rust::LANGUAGE.into()),
15147    )));
15148    let mut fake_servers = language_registry.register_fake_lsp(
15149        "Rust",
15150        FakeLspAdapter {
15151            capabilities: lsp::ServerCapabilities {
15152                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15153                    first_trigger_character: "{".to_string(),
15154                    more_trigger_character: None,
15155                }),
15156                ..Default::default()
15157            },
15158            ..Default::default()
15159        },
15160    );
15161
15162    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15163
15164    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15165
15166    let worktree_id = workspace
15167        .update(cx, |workspace, _, cx| {
15168            workspace.project().update(cx, |project, cx| {
15169                project.worktrees(cx).next().unwrap().read(cx).id()
15170            })
15171        })
15172        .unwrap();
15173
15174    let buffer = project
15175        .update(cx, |project, cx| {
15176            project.open_local_buffer(path!("/a/main.rs"), cx)
15177        })
15178        .await
15179        .unwrap();
15180    let editor_handle = workspace
15181        .update(cx, |workspace, window, cx| {
15182            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15183        })
15184        .unwrap()
15185        .await
15186        .unwrap()
15187        .downcast::<Editor>()
15188        .unwrap();
15189
15190    cx.executor().start_waiting();
15191    let fake_server = fake_servers.next().await.unwrap();
15192
15193    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15194        |params, _| async move {
15195            assert_eq!(
15196                params.text_document_position.text_document.uri,
15197                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15198            );
15199            assert_eq!(
15200                params.text_document_position.position,
15201                lsp::Position::new(0, 21),
15202            );
15203
15204            Ok(Some(vec![lsp::TextEdit {
15205                new_text: "]".to_string(),
15206                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15207            }]))
15208        },
15209    );
15210
15211    editor_handle.update_in(cx, |editor, window, cx| {
15212        window.focus(&editor.focus_handle(cx));
15213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15214            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15215        });
15216        editor.handle_input("{", window, cx);
15217    });
15218
15219    cx.executor().run_until_parked();
15220
15221    buffer.update(cx, |buffer, _| {
15222        assert_eq!(
15223            buffer.text(),
15224            "fn main() { let a = {5}; }",
15225            "No extra braces from on type formatting should appear in the buffer"
15226        )
15227    });
15228}
15229
15230#[gpui::test(iterations = 20, seeds(31))]
15231async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15232    init_test(cx, |_| {});
15233
15234    let mut cx = EditorLspTestContext::new_rust(
15235        lsp::ServerCapabilities {
15236            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15237                first_trigger_character: ".".to_string(),
15238                more_trigger_character: None,
15239            }),
15240            ..Default::default()
15241        },
15242        cx,
15243    )
15244    .await;
15245
15246    cx.update_buffer(|buffer, _| {
15247        // This causes autoindent to be async.
15248        buffer.set_sync_parse_timeout(Duration::ZERO)
15249    });
15250
15251    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15252    cx.simulate_keystroke("\n");
15253    cx.run_until_parked();
15254
15255    let buffer_cloned =
15256        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15257    let mut request =
15258        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15259            let buffer_cloned = buffer_cloned.clone();
15260            async move {
15261                buffer_cloned.update(&mut cx, |buffer, _| {
15262                    assert_eq!(
15263                        buffer.text(),
15264                        "fn c() {\n    d()\n        .\n}\n",
15265                        "OnTypeFormatting should triggered after autoindent applied"
15266                    )
15267                })?;
15268
15269                Ok(Some(vec![]))
15270            }
15271        });
15272
15273    cx.simulate_keystroke(".");
15274    cx.run_until_parked();
15275
15276    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15277    assert!(request.next().await.is_some());
15278    request.close();
15279    assert!(request.next().await.is_none());
15280}
15281
15282#[gpui::test]
15283async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15284    init_test(cx, |_| {});
15285
15286    let fs = FakeFs::new(cx.executor());
15287    fs.insert_tree(
15288        path!("/a"),
15289        json!({
15290            "main.rs": "fn main() { let a = 5; }",
15291            "other.rs": "// Test file",
15292        }),
15293    )
15294    .await;
15295
15296    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15297
15298    let server_restarts = Arc::new(AtomicUsize::new(0));
15299    let closure_restarts = Arc::clone(&server_restarts);
15300    let language_server_name = "test language server";
15301    let language_name: LanguageName = "Rust".into();
15302
15303    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15304    language_registry.add(Arc::new(Language::new(
15305        LanguageConfig {
15306            name: language_name.clone(),
15307            matcher: LanguageMatcher {
15308                path_suffixes: vec!["rs".to_string()],
15309                ..Default::default()
15310            },
15311            ..Default::default()
15312        },
15313        Some(tree_sitter_rust::LANGUAGE.into()),
15314    )));
15315    let mut fake_servers = language_registry.register_fake_lsp(
15316        "Rust",
15317        FakeLspAdapter {
15318            name: language_server_name,
15319            initialization_options: Some(json!({
15320                "testOptionValue": true
15321            })),
15322            initializer: Some(Box::new(move |fake_server| {
15323                let task_restarts = Arc::clone(&closure_restarts);
15324                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15325                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15326                    futures::future::ready(Ok(()))
15327                });
15328            })),
15329            ..Default::default()
15330        },
15331    );
15332
15333    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15334    let _buffer = project
15335        .update(cx, |project, cx| {
15336            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15337        })
15338        .await
15339        .unwrap();
15340    let _fake_server = fake_servers.next().await.unwrap();
15341    update_test_language_settings(cx, |language_settings| {
15342        language_settings.languages.0.insert(
15343            language_name.clone(),
15344            LanguageSettingsContent {
15345                tab_size: NonZeroU32::new(8),
15346                ..Default::default()
15347            },
15348        );
15349    });
15350    cx.executor().run_until_parked();
15351    assert_eq!(
15352        server_restarts.load(atomic::Ordering::Acquire),
15353        0,
15354        "Should not restart LSP server on an unrelated change"
15355    );
15356
15357    update_test_project_settings(cx, |project_settings| {
15358        project_settings.lsp.insert(
15359            "Some other server name".into(),
15360            LspSettings {
15361                binary: None,
15362                settings: None,
15363                initialization_options: Some(json!({
15364                    "some other init value": false
15365                })),
15366                enable_lsp_tasks: false,
15367            },
15368        );
15369    });
15370    cx.executor().run_until_parked();
15371    assert_eq!(
15372        server_restarts.load(atomic::Ordering::Acquire),
15373        0,
15374        "Should not restart LSP server on an unrelated LSP settings change"
15375    );
15376
15377    update_test_project_settings(cx, |project_settings| {
15378        project_settings.lsp.insert(
15379            language_server_name.into(),
15380            LspSettings {
15381                binary: None,
15382                settings: None,
15383                initialization_options: Some(json!({
15384                    "anotherInitValue": false
15385                })),
15386                enable_lsp_tasks: false,
15387            },
15388        );
15389    });
15390    cx.executor().run_until_parked();
15391    assert_eq!(
15392        server_restarts.load(atomic::Ordering::Acquire),
15393        1,
15394        "Should restart LSP server on a related LSP settings change"
15395    );
15396
15397    update_test_project_settings(cx, |project_settings| {
15398        project_settings.lsp.insert(
15399            language_server_name.into(),
15400            LspSettings {
15401                binary: None,
15402                settings: None,
15403                initialization_options: Some(json!({
15404                    "anotherInitValue": false
15405                })),
15406                enable_lsp_tasks: false,
15407            },
15408        );
15409    });
15410    cx.executor().run_until_parked();
15411    assert_eq!(
15412        server_restarts.load(atomic::Ordering::Acquire),
15413        1,
15414        "Should not restart LSP server on a related LSP settings change that is the same"
15415    );
15416
15417    update_test_project_settings(cx, |project_settings| {
15418        project_settings.lsp.insert(
15419            language_server_name.into(),
15420            LspSettings {
15421                binary: None,
15422                settings: None,
15423                initialization_options: None,
15424                enable_lsp_tasks: false,
15425            },
15426        );
15427    });
15428    cx.executor().run_until_parked();
15429    assert_eq!(
15430        server_restarts.load(atomic::Ordering::Acquire),
15431        2,
15432        "Should restart LSP server on another related LSP settings change"
15433    );
15434}
15435
15436#[gpui::test]
15437async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15438    init_test(cx, |_| {});
15439
15440    let mut cx = EditorLspTestContext::new_rust(
15441        lsp::ServerCapabilities {
15442            completion_provider: Some(lsp::CompletionOptions {
15443                trigger_characters: Some(vec![".".to_string()]),
15444                resolve_provider: Some(true),
15445                ..Default::default()
15446            }),
15447            ..Default::default()
15448        },
15449        cx,
15450    )
15451    .await;
15452
15453    cx.set_state("fn main() { let a = 2ˇ; }");
15454    cx.simulate_keystroke(".");
15455    let completion_item = lsp::CompletionItem {
15456        label: "some".into(),
15457        kind: Some(lsp::CompletionItemKind::SNIPPET),
15458        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15459        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15460            kind: lsp::MarkupKind::Markdown,
15461            value: "```rust\nSome(2)\n```".to_string(),
15462        })),
15463        deprecated: Some(false),
15464        sort_text: Some("fffffff2".to_string()),
15465        filter_text: Some("some".to_string()),
15466        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15467        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15468            range: lsp::Range {
15469                start: lsp::Position {
15470                    line: 0,
15471                    character: 22,
15472                },
15473                end: lsp::Position {
15474                    line: 0,
15475                    character: 22,
15476                },
15477            },
15478            new_text: "Some(2)".to_string(),
15479        })),
15480        additional_text_edits: Some(vec![lsp::TextEdit {
15481            range: lsp::Range {
15482                start: lsp::Position {
15483                    line: 0,
15484                    character: 20,
15485                },
15486                end: lsp::Position {
15487                    line: 0,
15488                    character: 22,
15489                },
15490            },
15491            new_text: "".to_string(),
15492        }]),
15493        ..Default::default()
15494    };
15495
15496    let closure_completion_item = completion_item.clone();
15497    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15498        let task_completion_item = closure_completion_item.clone();
15499        async move {
15500            Ok(Some(lsp::CompletionResponse::Array(vec![
15501                task_completion_item,
15502            ])))
15503        }
15504    });
15505
15506    request.next().await;
15507
15508    cx.condition(|editor, _| editor.context_menu_visible())
15509        .await;
15510    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15511        editor
15512            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15513            .unwrap()
15514    });
15515    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15516
15517    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15518        let task_completion_item = completion_item.clone();
15519        async move { Ok(task_completion_item) }
15520    })
15521    .next()
15522    .await
15523    .unwrap();
15524    apply_additional_edits.await.unwrap();
15525    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15526}
15527
15528#[gpui::test]
15529async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15530    init_test(cx, |_| {});
15531
15532    let mut cx = EditorLspTestContext::new_rust(
15533        lsp::ServerCapabilities {
15534            completion_provider: Some(lsp::CompletionOptions {
15535                trigger_characters: Some(vec![".".to_string()]),
15536                resolve_provider: Some(true),
15537                ..Default::default()
15538            }),
15539            ..Default::default()
15540        },
15541        cx,
15542    )
15543    .await;
15544
15545    cx.set_state("fn main() { let a = 2ˇ; }");
15546    cx.simulate_keystroke(".");
15547
15548    let item1 = lsp::CompletionItem {
15549        label: "method id()".to_string(),
15550        filter_text: Some("id".to_string()),
15551        detail: None,
15552        documentation: None,
15553        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15554            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15555            new_text: ".id".to_string(),
15556        })),
15557        ..lsp::CompletionItem::default()
15558    };
15559
15560    let item2 = lsp::CompletionItem {
15561        label: "other".to_string(),
15562        filter_text: Some("other".to_string()),
15563        detail: None,
15564        documentation: None,
15565        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15566            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15567            new_text: ".other".to_string(),
15568        })),
15569        ..lsp::CompletionItem::default()
15570    };
15571
15572    let item1 = item1.clone();
15573    cx.set_request_handler::<lsp::request::Completion, _, _>({
15574        let item1 = item1.clone();
15575        move |_, _, _| {
15576            let item1 = item1.clone();
15577            let item2 = item2.clone();
15578            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15579        }
15580    })
15581    .next()
15582    .await;
15583
15584    cx.condition(|editor, _| editor.context_menu_visible())
15585        .await;
15586    cx.update_editor(|editor, _, _| {
15587        let context_menu = editor.context_menu.borrow_mut();
15588        let context_menu = context_menu
15589            .as_ref()
15590            .expect("Should have the context menu deployed");
15591        match context_menu {
15592            CodeContextMenu::Completions(completions_menu) => {
15593                let completions = completions_menu.completions.borrow_mut();
15594                assert_eq!(
15595                    completions
15596                        .iter()
15597                        .map(|completion| &completion.label.text)
15598                        .collect::<Vec<_>>(),
15599                    vec!["method id()", "other"]
15600                )
15601            }
15602            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15603        }
15604    });
15605
15606    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15607        let item1 = item1.clone();
15608        move |_, item_to_resolve, _| {
15609            let item1 = item1.clone();
15610            async move {
15611                if item1 == item_to_resolve {
15612                    Ok(lsp::CompletionItem {
15613                        label: "method id()".to_string(),
15614                        filter_text: Some("id".to_string()),
15615                        detail: Some("Now resolved!".to_string()),
15616                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15617                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15618                            range: lsp::Range::new(
15619                                lsp::Position::new(0, 22),
15620                                lsp::Position::new(0, 22),
15621                            ),
15622                            new_text: ".id".to_string(),
15623                        })),
15624                        ..lsp::CompletionItem::default()
15625                    })
15626                } else {
15627                    Ok(item_to_resolve)
15628                }
15629            }
15630        }
15631    })
15632    .next()
15633    .await
15634    .unwrap();
15635    cx.run_until_parked();
15636
15637    cx.update_editor(|editor, window, cx| {
15638        editor.context_menu_next(&Default::default(), window, cx);
15639    });
15640
15641    cx.update_editor(|editor, _, _| {
15642        let context_menu = editor.context_menu.borrow_mut();
15643        let context_menu = context_menu
15644            .as_ref()
15645            .expect("Should have the context menu deployed");
15646        match context_menu {
15647            CodeContextMenu::Completions(completions_menu) => {
15648                let completions = completions_menu.completions.borrow_mut();
15649                assert_eq!(
15650                    completions
15651                        .iter()
15652                        .map(|completion| &completion.label.text)
15653                        .collect::<Vec<_>>(),
15654                    vec!["method id() Now resolved!", "other"],
15655                    "Should update first completion label, but not second as the filter text did not match."
15656                );
15657            }
15658            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15659        }
15660    });
15661}
15662
15663#[gpui::test]
15664async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15665    init_test(cx, |_| {});
15666    let mut cx = EditorLspTestContext::new_rust(
15667        lsp::ServerCapabilities {
15668            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15669            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15670            completion_provider: Some(lsp::CompletionOptions {
15671                resolve_provider: Some(true),
15672                ..Default::default()
15673            }),
15674            ..Default::default()
15675        },
15676        cx,
15677    )
15678    .await;
15679    cx.set_state(indoc! {"
15680        struct TestStruct {
15681            field: i32
15682        }
15683
15684        fn mainˇ() {
15685            let unused_var = 42;
15686            let test_struct = TestStruct { field: 42 };
15687        }
15688    "});
15689    let symbol_range = cx.lsp_range(indoc! {"
15690        struct TestStruct {
15691            field: i32
15692        }
15693
15694        «fn main»() {
15695            let unused_var = 42;
15696            let test_struct = TestStruct { field: 42 };
15697        }
15698    "});
15699    let mut hover_requests =
15700        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15701            Ok(Some(lsp::Hover {
15702                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15703                    kind: lsp::MarkupKind::Markdown,
15704                    value: "Function documentation".to_string(),
15705                }),
15706                range: Some(symbol_range),
15707            }))
15708        });
15709
15710    // Case 1: Test that code action menu hide hover popover
15711    cx.dispatch_action(Hover);
15712    hover_requests.next().await;
15713    cx.condition(|editor, _| editor.hover_state.visible()).await;
15714    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15715        move |_, _, _| async move {
15716            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15717                lsp::CodeAction {
15718                    title: "Remove unused variable".to_string(),
15719                    kind: Some(CodeActionKind::QUICKFIX),
15720                    edit: Some(lsp::WorkspaceEdit {
15721                        changes: Some(
15722                            [(
15723                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15724                                vec![lsp::TextEdit {
15725                                    range: lsp::Range::new(
15726                                        lsp::Position::new(5, 4),
15727                                        lsp::Position::new(5, 27),
15728                                    ),
15729                                    new_text: "".to_string(),
15730                                }],
15731                            )]
15732                            .into_iter()
15733                            .collect(),
15734                        ),
15735                        ..Default::default()
15736                    }),
15737                    ..Default::default()
15738                },
15739            )]))
15740        },
15741    );
15742    cx.update_editor(|editor, window, cx| {
15743        editor.toggle_code_actions(
15744            &ToggleCodeActions {
15745                deployed_from: None,
15746                quick_launch: false,
15747            },
15748            window,
15749            cx,
15750        );
15751    });
15752    code_action_requests.next().await;
15753    cx.run_until_parked();
15754    cx.condition(|editor, _| editor.context_menu_visible())
15755        .await;
15756    cx.update_editor(|editor, _, _| {
15757        assert!(
15758            !editor.hover_state.visible(),
15759            "Hover popover should be hidden when code action menu is shown"
15760        );
15761        // Hide code actions
15762        editor.context_menu.take();
15763    });
15764
15765    // Case 2: Test that code completions hide hover popover
15766    cx.dispatch_action(Hover);
15767    hover_requests.next().await;
15768    cx.condition(|editor, _| editor.hover_state.visible()).await;
15769    let counter = Arc::new(AtomicUsize::new(0));
15770    let mut completion_requests =
15771        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15772            let counter = counter.clone();
15773            async move {
15774                counter.fetch_add(1, atomic::Ordering::Release);
15775                Ok(Some(lsp::CompletionResponse::Array(vec![
15776                    lsp::CompletionItem {
15777                        label: "main".into(),
15778                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15779                        detail: Some("() -> ()".to_string()),
15780                        ..Default::default()
15781                    },
15782                    lsp::CompletionItem {
15783                        label: "TestStruct".into(),
15784                        kind: Some(lsp::CompletionItemKind::STRUCT),
15785                        detail: Some("struct TestStruct".to_string()),
15786                        ..Default::default()
15787                    },
15788                ])))
15789            }
15790        });
15791    cx.update_editor(|editor, window, cx| {
15792        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15793    });
15794    completion_requests.next().await;
15795    cx.condition(|editor, _| editor.context_menu_visible())
15796        .await;
15797    cx.update_editor(|editor, _, _| {
15798        assert!(
15799            !editor.hover_state.visible(),
15800            "Hover popover should be hidden when completion menu is shown"
15801        );
15802    });
15803}
15804
15805#[gpui::test]
15806async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15807    init_test(cx, |_| {});
15808
15809    let mut cx = EditorLspTestContext::new_rust(
15810        lsp::ServerCapabilities {
15811            completion_provider: Some(lsp::CompletionOptions {
15812                trigger_characters: Some(vec![".".to_string()]),
15813                resolve_provider: Some(true),
15814                ..Default::default()
15815            }),
15816            ..Default::default()
15817        },
15818        cx,
15819    )
15820    .await;
15821
15822    cx.set_state("fn main() { let a = 2ˇ; }");
15823    cx.simulate_keystroke(".");
15824
15825    let unresolved_item_1 = lsp::CompletionItem {
15826        label: "id".to_string(),
15827        filter_text: Some("id".to_string()),
15828        detail: None,
15829        documentation: None,
15830        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15831            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15832            new_text: ".id".to_string(),
15833        })),
15834        ..lsp::CompletionItem::default()
15835    };
15836    let resolved_item_1 = lsp::CompletionItem {
15837        additional_text_edits: Some(vec![lsp::TextEdit {
15838            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15839            new_text: "!!".to_string(),
15840        }]),
15841        ..unresolved_item_1.clone()
15842    };
15843    let unresolved_item_2 = lsp::CompletionItem {
15844        label: "other".to_string(),
15845        filter_text: Some("other".to_string()),
15846        detail: None,
15847        documentation: None,
15848        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15849            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15850            new_text: ".other".to_string(),
15851        })),
15852        ..lsp::CompletionItem::default()
15853    };
15854    let resolved_item_2 = lsp::CompletionItem {
15855        additional_text_edits: Some(vec![lsp::TextEdit {
15856            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15857            new_text: "??".to_string(),
15858        }]),
15859        ..unresolved_item_2.clone()
15860    };
15861
15862    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15863    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15864    cx.lsp
15865        .server
15866        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15867            let unresolved_item_1 = unresolved_item_1.clone();
15868            let resolved_item_1 = resolved_item_1.clone();
15869            let unresolved_item_2 = unresolved_item_2.clone();
15870            let resolved_item_2 = resolved_item_2.clone();
15871            let resolve_requests_1 = resolve_requests_1.clone();
15872            let resolve_requests_2 = resolve_requests_2.clone();
15873            move |unresolved_request, _| {
15874                let unresolved_item_1 = unresolved_item_1.clone();
15875                let resolved_item_1 = resolved_item_1.clone();
15876                let unresolved_item_2 = unresolved_item_2.clone();
15877                let resolved_item_2 = resolved_item_2.clone();
15878                let resolve_requests_1 = resolve_requests_1.clone();
15879                let resolve_requests_2 = resolve_requests_2.clone();
15880                async move {
15881                    if unresolved_request == unresolved_item_1 {
15882                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15883                        Ok(resolved_item_1.clone())
15884                    } else if unresolved_request == unresolved_item_2 {
15885                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15886                        Ok(resolved_item_2.clone())
15887                    } else {
15888                        panic!("Unexpected completion item {unresolved_request:?}")
15889                    }
15890                }
15891            }
15892        })
15893        .detach();
15894
15895    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15896        let unresolved_item_1 = unresolved_item_1.clone();
15897        let unresolved_item_2 = unresolved_item_2.clone();
15898        async move {
15899            Ok(Some(lsp::CompletionResponse::Array(vec![
15900                unresolved_item_1,
15901                unresolved_item_2,
15902            ])))
15903        }
15904    })
15905    .next()
15906    .await;
15907
15908    cx.condition(|editor, _| editor.context_menu_visible())
15909        .await;
15910    cx.update_editor(|editor, _, _| {
15911        let context_menu = editor.context_menu.borrow_mut();
15912        let context_menu = context_menu
15913            .as_ref()
15914            .expect("Should have the context menu deployed");
15915        match context_menu {
15916            CodeContextMenu::Completions(completions_menu) => {
15917                let completions = completions_menu.completions.borrow_mut();
15918                assert_eq!(
15919                    completions
15920                        .iter()
15921                        .map(|completion| &completion.label.text)
15922                        .collect::<Vec<_>>(),
15923                    vec!["id", "other"]
15924                )
15925            }
15926            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15927        }
15928    });
15929    cx.run_until_parked();
15930
15931    cx.update_editor(|editor, window, cx| {
15932        editor.context_menu_next(&ContextMenuNext, window, cx);
15933    });
15934    cx.run_until_parked();
15935    cx.update_editor(|editor, window, cx| {
15936        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15937    });
15938    cx.run_until_parked();
15939    cx.update_editor(|editor, window, cx| {
15940        editor.context_menu_next(&ContextMenuNext, window, cx);
15941    });
15942    cx.run_until_parked();
15943    cx.update_editor(|editor, window, cx| {
15944        editor
15945            .compose_completion(&ComposeCompletion::default(), window, cx)
15946            .expect("No task returned")
15947    })
15948    .await
15949    .expect("Completion failed");
15950    cx.run_until_parked();
15951
15952    cx.update_editor(|editor, _, cx| {
15953        assert_eq!(
15954            resolve_requests_1.load(atomic::Ordering::Acquire),
15955            1,
15956            "Should always resolve once despite multiple selections"
15957        );
15958        assert_eq!(
15959            resolve_requests_2.load(atomic::Ordering::Acquire),
15960            1,
15961            "Should always resolve once after multiple selections and applying the completion"
15962        );
15963        assert_eq!(
15964            editor.text(cx),
15965            "fn main() { let a = ??.other; }",
15966            "Should use resolved data when applying the completion"
15967        );
15968    });
15969}
15970
15971#[gpui::test]
15972async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15973    init_test(cx, |_| {});
15974
15975    let item_0 = lsp::CompletionItem {
15976        label: "abs".into(),
15977        insert_text: Some("abs".into()),
15978        data: Some(json!({ "very": "special"})),
15979        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15980        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15981            lsp::InsertReplaceEdit {
15982                new_text: "abs".to_string(),
15983                insert: lsp::Range::default(),
15984                replace: lsp::Range::default(),
15985            },
15986        )),
15987        ..lsp::CompletionItem::default()
15988    };
15989    let items = iter::once(item_0.clone())
15990        .chain((11..51).map(|i| lsp::CompletionItem {
15991            label: format!("item_{}", i),
15992            insert_text: Some(format!("item_{}", i)),
15993            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15994            ..lsp::CompletionItem::default()
15995        }))
15996        .collect::<Vec<_>>();
15997
15998    let default_commit_characters = vec!["?".to_string()];
15999    let default_data = json!({ "default": "data"});
16000    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16001    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16002    let default_edit_range = lsp::Range {
16003        start: lsp::Position {
16004            line: 0,
16005            character: 5,
16006        },
16007        end: lsp::Position {
16008            line: 0,
16009            character: 5,
16010        },
16011    };
16012
16013    let mut cx = EditorLspTestContext::new_rust(
16014        lsp::ServerCapabilities {
16015            completion_provider: Some(lsp::CompletionOptions {
16016                trigger_characters: Some(vec![".".to_string()]),
16017                resolve_provider: Some(true),
16018                ..Default::default()
16019            }),
16020            ..Default::default()
16021        },
16022        cx,
16023    )
16024    .await;
16025
16026    cx.set_state("fn main() { let a = 2ˇ; }");
16027    cx.simulate_keystroke(".");
16028
16029    let completion_data = default_data.clone();
16030    let completion_characters = default_commit_characters.clone();
16031    let completion_items = items.clone();
16032    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16033        let default_data = completion_data.clone();
16034        let default_commit_characters = completion_characters.clone();
16035        let items = completion_items.clone();
16036        async move {
16037            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16038                items,
16039                item_defaults: Some(lsp::CompletionListItemDefaults {
16040                    data: Some(default_data.clone()),
16041                    commit_characters: Some(default_commit_characters.clone()),
16042                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16043                        default_edit_range,
16044                    )),
16045                    insert_text_format: Some(default_insert_text_format),
16046                    insert_text_mode: Some(default_insert_text_mode),
16047                }),
16048                ..lsp::CompletionList::default()
16049            })))
16050        }
16051    })
16052    .next()
16053    .await;
16054
16055    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16056    cx.lsp
16057        .server
16058        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16059            let closure_resolved_items = resolved_items.clone();
16060            move |item_to_resolve, _| {
16061                let closure_resolved_items = closure_resolved_items.clone();
16062                async move {
16063                    closure_resolved_items.lock().push(item_to_resolve.clone());
16064                    Ok(item_to_resolve)
16065                }
16066            }
16067        })
16068        .detach();
16069
16070    cx.condition(|editor, _| editor.context_menu_visible())
16071        .await;
16072    cx.run_until_parked();
16073    cx.update_editor(|editor, _, _| {
16074        let menu = editor.context_menu.borrow_mut();
16075        match menu.as_ref().expect("should have the completions menu") {
16076            CodeContextMenu::Completions(completions_menu) => {
16077                assert_eq!(
16078                    completions_menu
16079                        .entries
16080                        .borrow()
16081                        .iter()
16082                        .map(|mat| mat.string.clone())
16083                        .collect::<Vec<String>>(),
16084                    items
16085                        .iter()
16086                        .map(|completion| completion.label.clone())
16087                        .collect::<Vec<String>>()
16088                );
16089            }
16090            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16091        }
16092    });
16093    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16094    // with 4 from the end.
16095    assert_eq!(
16096        *resolved_items.lock(),
16097        [&items[0..16], &items[items.len() - 4..items.len()]]
16098            .concat()
16099            .iter()
16100            .cloned()
16101            .map(|mut item| {
16102                if item.data.is_none() {
16103                    item.data = Some(default_data.clone());
16104                }
16105                item
16106            })
16107            .collect::<Vec<lsp::CompletionItem>>(),
16108        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16109    );
16110    resolved_items.lock().clear();
16111
16112    cx.update_editor(|editor, window, cx| {
16113        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16114    });
16115    cx.run_until_parked();
16116    // Completions that have already been resolved are skipped.
16117    assert_eq!(
16118        *resolved_items.lock(),
16119        items[items.len() - 17..items.len() - 4]
16120            .iter()
16121            .cloned()
16122            .map(|mut item| {
16123                if item.data.is_none() {
16124                    item.data = Some(default_data.clone());
16125                }
16126                item
16127            })
16128            .collect::<Vec<lsp::CompletionItem>>()
16129    );
16130    resolved_items.lock().clear();
16131}
16132
16133#[gpui::test]
16134async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16135    init_test(cx, |_| {});
16136
16137    let mut cx = EditorLspTestContext::new(
16138        Language::new(
16139            LanguageConfig {
16140                matcher: LanguageMatcher {
16141                    path_suffixes: vec!["jsx".into()],
16142                    ..Default::default()
16143                },
16144                overrides: [(
16145                    "element".into(),
16146                    LanguageConfigOverride {
16147                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16148                        ..Default::default()
16149                    },
16150                )]
16151                .into_iter()
16152                .collect(),
16153                ..Default::default()
16154            },
16155            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16156        )
16157        .with_override_query("(jsx_self_closing_element) @element")
16158        .unwrap(),
16159        lsp::ServerCapabilities {
16160            completion_provider: Some(lsp::CompletionOptions {
16161                trigger_characters: Some(vec![":".to_string()]),
16162                ..Default::default()
16163            }),
16164            ..Default::default()
16165        },
16166        cx,
16167    )
16168    .await;
16169
16170    cx.lsp
16171        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16172            Ok(Some(lsp::CompletionResponse::Array(vec![
16173                lsp::CompletionItem {
16174                    label: "bg-blue".into(),
16175                    ..Default::default()
16176                },
16177                lsp::CompletionItem {
16178                    label: "bg-red".into(),
16179                    ..Default::default()
16180                },
16181                lsp::CompletionItem {
16182                    label: "bg-yellow".into(),
16183                    ..Default::default()
16184                },
16185            ])))
16186        });
16187
16188    cx.set_state(r#"<p class="bgˇ" />"#);
16189
16190    // Trigger completion when typing a dash, because the dash is an extra
16191    // word character in the 'element' scope, which contains the cursor.
16192    cx.simulate_keystroke("-");
16193    cx.executor().run_until_parked();
16194    cx.update_editor(|editor, _, _| {
16195        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16196        {
16197            assert_eq!(
16198                completion_menu_entries(&menu),
16199                &["bg-blue", "bg-red", "bg-yellow"]
16200            );
16201        } else {
16202            panic!("expected completion menu to be open");
16203        }
16204    });
16205
16206    cx.simulate_keystroke("l");
16207    cx.executor().run_until_parked();
16208    cx.update_editor(|editor, _, _| {
16209        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16210        {
16211            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16212        } else {
16213            panic!("expected completion menu to be open");
16214        }
16215    });
16216
16217    // When filtering completions, consider the character after the '-' to
16218    // be the start of a subword.
16219    cx.set_state(r#"<p class="yelˇ" />"#);
16220    cx.simulate_keystroke("l");
16221    cx.executor().run_until_parked();
16222    cx.update_editor(|editor, _, _| {
16223        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16224        {
16225            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16226        } else {
16227            panic!("expected completion menu to be open");
16228        }
16229    });
16230}
16231
16232fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16233    let entries = menu.entries.borrow();
16234    entries.iter().map(|mat| mat.string.clone()).collect()
16235}
16236
16237#[gpui::test]
16238async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16239    init_test(cx, |settings| {
16240        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16241            Formatter::Prettier,
16242        )))
16243    });
16244
16245    let fs = FakeFs::new(cx.executor());
16246    fs.insert_file(path!("/file.ts"), Default::default()).await;
16247
16248    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16249    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16250
16251    language_registry.add(Arc::new(Language::new(
16252        LanguageConfig {
16253            name: "TypeScript".into(),
16254            matcher: LanguageMatcher {
16255                path_suffixes: vec!["ts".to_string()],
16256                ..Default::default()
16257            },
16258            ..Default::default()
16259        },
16260        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16261    )));
16262    update_test_language_settings(cx, |settings| {
16263        settings.defaults.prettier = Some(PrettierSettings {
16264            allowed: true,
16265            ..PrettierSettings::default()
16266        });
16267    });
16268
16269    let test_plugin = "test_plugin";
16270    let _ = language_registry.register_fake_lsp(
16271        "TypeScript",
16272        FakeLspAdapter {
16273            prettier_plugins: vec![test_plugin],
16274            ..Default::default()
16275        },
16276    );
16277
16278    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16279    let buffer = project
16280        .update(cx, |project, cx| {
16281            project.open_local_buffer(path!("/file.ts"), cx)
16282        })
16283        .await
16284        .unwrap();
16285
16286    let buffer_text = "one\ntwo\nthree\n";
16287    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16288    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16289    editor.update_in(cx, |editor, window, cx| {
16290        editor.set_text(buffer_text, window, cx)
16291    });
16292
16293    editor
16294        .update_in(cx, |editor, window, cx| {
16295            editor.perform_format(
16296                project.clone(),
16297                FormatTrigger::Manual,
16298                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16299                window,
16300                cx,
16301            )
16302        })
16303        .unwrap()
16304        .await;
16305    assert_eq!(
16306        editor.update(cx, |editor, cx| editor.text(cx)),
16307        buffer_text.to_string() + prettier_format_suffix,
16308        "Test prettier formatting was not applied to the original buffer text",
16309    );
16310
16311    update_test_language_settings(cx, |settings| {
16312        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16313    });
16314    let format = editor.update_in(cx, |editor, window, cx| {
16315        editor.perform_format(
16316            project.clone(),
16317            FormatTrigger::Manual,
16318            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16319            window,
16320            cx,
16321        )
16322    });
16323    format.await.unwrap();
16324    assert_eq!(
16325        editor.update(cx, |editor, cx| editor.text(cx)),
16326        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16327        "Autoformatting (via test prettier) was not applied to the original buffer text",
16328    );
16329}
16330
16331#[gpui::test]
16332async fn test_addition_reverts(cx: &mut TestAppContext) {
16333    init_test(cx, |_| {});
16334    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16335    let base_text = indoc! {r#"
16336        struct Row;
16337        struct Row1;
16338        struct Row2;
16339
16340        struct Row4;
16341        struct Row5;
16342        struct Row6;
16343
16344        struct Row8;
16345        struct Row9;
16346        struct Row10;"#};
16347
16348    // When addition hunks are not adjacent to carets, no hunk revert is performed
16349    assert_hunk_revert(
16350        indoc! {r#"struct Row;
16351                   struct Row1;
16352                   struct Row1.1;
16353                   struct Row1.2;
16354                   struct Row2;ˇ
16355
16356                   struct Row4;
16357                   struct Row5;
16358                   struct Row6;
16359
16360                   struct Row8;
16361                   ˇstruct Row9;
16362                   struct Row9.1;
16363                   struct Row9.2;
16364                   struct Row9.3;
16365                   struct Row10;"#},
16366        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16367        indoc! {r#"struct Row;
16368                   struct Row1;
16369                   struct Row1.1;
16370                   struct Row1.2;
16371                   struct Row2;ˇ
16372
16373                   struct Row4;
16374                   struct Row5;
16375                   struct Row6;
16376
16377                   struct Row8;
16378                   ˇstruct Row9;
16379                   struct Row9.1;
16380                   struct Row9.2;
16381                   struct Row9.3;
16382                   struct Row10;"#},
16383        base_text,
16384        &mut cx,
16385    );
16386    // Same for selections
16387    assert_hunk_revert(
16388        indoc! {r#"struct Row;
16389                   struct Row1;
16390                   struct Row2;
16391                   struct Row2.1;
16392                   struct Row2.2;
16393                   «ˇ
16394                   struct Row4;
16395                   struct» Row5;
16396                   «struct Row6;
16397                   ˇ»
16398                   struct Row9.1;
16399                   struct Row9.2;
16400                   struct Row9.3;
16401                   struct Row8;
16402                   struct Row9;
16403                   struct Row10;"#},
16404        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16405        indoc! {r#"struct Row;
16406                   struct Row1;
16407                   struct Row2;
16408                   struct Row2.1;
16409                   struct Row2.2;
16410                   «ˇ
16411                   struct Row4;
16412                   struct» Row5;
16413                   «struct Row6;
16414                   ˇ»
16415                   struct Row9.1;
16416                   struct Row9.2;
16417                   struct Row9.3;
16418                   struct Row8;
16419                   struct Row9;
16420                   struct Row10;"#},
16421        base_text,
16422        &mut cx,
16423    );
16424
16425    // When carets and selections intersect the addition hunks, those are reverted.
16426    // Adjacent carets got merged.
16427    assert_hunk_revert(
16428        indoc! {r#"struct Row;
16429                   ˇ// something on the top
16430                   struct Row1;
16431                   struct Row2;
16432                   struct Roˇw3.1;
16433                   struct Row2.2;
16434                   struct Row2.3;ˇ
16435
16436                   struct Row4;
16437                   struct ˇRow5.1;
16438                   struct Row5.2;
16439                   struct «Rowˇ»5.3;
16440                   struct Row5;
16441                   struct Row6;
16442                   ˇ
16443                   struct Row9.1;
16444                   struct «Rowˇ»9.2;
16445                   struct «ˇRow»9.3;
16446                   struct Row8;
16447                   struct Row9;
16448                   «ˇ// something on bottom»
16449                   struct Row10;"#},
16450        vec![
16451            DiffHunkStatusKind::Added,
16452            DiffHunkStatusKind::Added,
16453            DiffHunkStatusKind::Added,
16454            DiffHunkStatusKind::Added,
16455            DiffHunkStatusKind::Added,
16456        ],
16457        indoc! {r#"struct Row;
16458                   ˇstruct Row1;
16459                   struct Row2;
16460                   ˇ
16461                   struct Row4;
16462                   ˇstruct Row5;
16463                   struct Row6;
16464                   ˇ
16465                   ˇstruct Row8;
16466                   struct Row9;
16467                   ˇstruct Row10;"#},
16468        base_text,
16469        &mut cx,
16470    );
16471}
16472
16473#[gpui::test]
16474async fn test_modification_reverts(cx: &mut TestAppContext) {
16475    init_test(cx, |_| {});
16476    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16477    let base_text = indoc! {r#"
16478        struct Row;
16479        struct Row1;
16480        struct Row2;
16481
16482        struct Row4;
16483        struct Row5;
16484        struct Row6;
16485
16486        struct Row8;
16487        struct Row9;
16488        struct Row10;"#};
16489
16490    // Modification hunks behave the same as the addition ones.
16491    assert_hunk_revert(
16492        indoc! {r#"struct Row;
16493                   struct Row1;
16494                   struct Row33;
16495                   ˇ
16496                   struct Row4;
16497                   struct Row5;
16498                   struct Row6;
16499                   ˇ
16500                   struct Row99;
16501                   struct Row9;
16502                   struct Row10;"#},
16503        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16504        indoc! {r#"struct Row;
16505                   struct Row1;
16506                   struct Row33;
16507                   ˇ
16508                   struct Row4;
16509                   struct Row5;
16510                   struct Row6;
16511                   ˇ
16512                   struct Row99;
16513                   struct Row9;
16514                   struct Row10;"#},
16515        base_text,
16516        &mut cx,
16517    );
16518    assert_hunk_revert(
16519        indoc! {r#"struct Row;
16520                   struct Row1;
16521                   struct Row33;
16522                   «ˇ
16523                   struct Row4;
16524                   struct» Row5;
16525                   «struct Row6;
16526                   ˇ»
16527                   struct Row99;
16528                   struct Row9;
16529                   struct Row10;"#},
16530        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16531        indoc! {r#"struct Row;
16532                   struct Row1;
16533                   struct Row33;
16534                   «ˇ
16535                   struct Row4;
16536                   struct» Row5;
16537                   «struct Row6;
16538                   ˇ»
16539                   struct Row99;
16540                   struct Row9;
16541                   struct Row10;"#},
16542        base_text,
16543        &mut cx,
16544    );
16545
16546    assert_hunk_revert(
16547        indoc! {r#"ˇstruct Row1.1;
16548                   struct Row1;
16549                   «ˇstr»uct Row22;
16550
16551                   struct ˇRow44;
16552                   struct Row5;
16553                   struct «Rˇ»ow66;ˇ
16554
16555                   «struˇ»ct Row88;
16556                   struct Row9;
16557                   struct Row1011;ˇ"#},
16558        vec![
16559            DiffHunkStatusKind::Modified,
16560            DiffHunkStatusKind::Modified,
16561            DiffHunkStatusKind::Modified,
16562            DiffHunkStatusKind::Modified,
16563            DiffHunkStatusKind::Modified,
16564            DiffHunkStatusKind::Modified,
16565        ],
16566        indoc! {r#"struct Row;
16567                   ˇstruct Row1;
16568                   struct Row2;
16569                   ˇ
16570                   struct Row4;
16571                   ˇstruct Row5;
16572                   struct Row6;
16573                   ˇ
16574                   struct Row8;
16575                   ˇstruct Row9;
16576                   struct Row10;ˇ"#},
16577        base_text,
16578        &mut cx,
16579    );
16580}
16581
16582#[gpui::test]
16583async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16584    init_test(cx, |_| {});
16585    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16586    let base_text = indoc! {r#"
16587        one
16588
16589        two
16590        three
16591        "#};
16592
16593    cx.set_head_text(base_text);
16594    cx.set_state("\nˇ\n");
16595    cx.executor().run_until_parked();
16596    cx.update_editor(|editor, _window, cx| {
16597        editor.expand_selected_diff_hunks(cx);
16598    });
16599    cx.executor().run_until_parked();
16600    cx.update_editor(|editor, window, cx| {
16601        editor.backspace(&Default::default(), window, cx);
16602    });
16603    cx.run_until_parked();
16604    cx.assert_state_with_diff(
16605        indoc! {r#"
16606
16607        - two
16608        - threeˇ
16609        +
16610        "#}
16611        .to_string(),
16612    );
16613}
16614
16615#[gpui::test]
16616async fn test_deletion_reverts(cx: &mut TestAppContext) {
16617    init_test(cx, |_| {});
16618    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16619    let base_text = indoc! {r#"struct Row;
16620struct Row1;
16621struct Row2;
16622
16623struct Row4;
16624struct Row5;
16625struct Row6;
16626
16627struct Row8;
16628struct Row9;
16629struct Row10;"#};
16630
16631    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16632    assert_hunk_revert(
16633        indoc! {r#"struct Row;
16634                   struct Row2;
16635
16636                   ˇstruct Row4;
16637                   struct Row5;
16638                   struct Row6;
16639                   ˇ
16640                   struct Row8;
16641                   struct Row10;"#},
16642        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16643        indoc! {r#"struct Row;
16644                   struct Row2;
16645
16646                   ˇstruct Row4;
16647                   struct Row5;
16648                   struct Row6;
16649                   ˇ
16650                   struct Row8;
16651                   struct Row10;"#},
16652        base_text,
16653        &mut cx,
16654    );
16655    assert_hunk_revert(
16656        indoc! {r#"struct Row;
16657                   struct Row2;
16658
16659                   «ˇstruct Row4;
16660                   struct» Row5;
16661                   «struct Row6;
16662                   ˇ»
16663                   struct Row8;
16664                   struct Row10;"#},
16665        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16666        indoc! {r#"struct Row;
16667                   struct Row2;
16668
16669                   «ˇstruct Row4;
16670                   struct» Row5;
16671                   «struct Row6;
16672                   ˇ»
16673                   struct Row8;
16674                   struct Row10;"#},
16675        base_text,
16676        &mut cx,
16677    );
16678
16679    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16680    assert_hunk_revert(
16681        indoc! {r#"struct Row;
16682                   ˇstruct Row2;
16683
16684                   struct Row4;
16685                   struct Row5;
16686                   struct Row6;
16687
16688                   struct Row8;ˇ
16689                   struct Row10;"#},
16690        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16691        indoc! {r#"struct Row;
16692                   struct Row1;
16693                   ˇstruct Row2;
16694
16695                   struct Row4;
16696                   struct Row5;
16697                   struct Row6;
16698
16699                   struct Row8;ˇ
16700                   struct Row9;
16701                   struct Row10;"#},
16702        base_text,
16703        &mut cx,
16704    );
16705    assert_hunk_revert(
16706        indoc! {r#"struct Row;
16707                   struct Row2«ˇ;
16708                   struct Row4;
16709                   struct» Row5;
16710                   «struct Row6;
16711
16712                   struct Row8;ˇ»
16713                   struct Row10;"#},
16714        vec![
16715            DiffHunkStatusKind::Deleted,
16716            DiffHunkStatusKind::Deleted,
16717            DiffHunkStatusKind::Deleted,
16718        ],
16719        indoc! {r#"struct Row;
16720                   struct Row1;
16721                   struct Row2«ˇ;
16722
16723                   struct Row4;
16724                   struct» Row5;
16725                   «struct Row6;
16726
16727                   struct Row8;ˇ»
16728                   struct Row9;
16729                   struct Row10;"#},
16730        base_text,
16731        &mut cx,
16732    );
16733}
16734
16735#[gpui::test]
16736async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16737    init_test(cx, |_| {});
16738
16739    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16740    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16741    let base_text_3 =
16742        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16743
16744    let text_1 = edit_first_char_of_every_line(base_text_1);
16745    let text_2 = edit_first_char_of_every_line(base_text_2);
16746    let text_3 = edit_first_char_of_every_line(base_text_3);
16747
16748    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16749    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16750    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16751
16752    let multibuffer = cx.new(|cx| {
16753        let mut multibuffer = MultiBuffer::new(ReadWrite);
16754        multibuffer.push_excerpts(
16755            buffer_1.clone(),
16756            [
16757                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16758                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16759                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16760            ],
16761            cx,
16762        );
16763        multibuffer.push_excerpts(
16764            buffer_2.clone(),
16765            [
16766                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16767                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16768                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16769            ],
16770            cx,
16771        );
16772        multibuffer.push_excerpts(
16773            buffer_3.clone(),
16774            [
16775                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16776                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16777                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16778            ],
16779            cx,
16780        );
16781        multibuffer
16782    });
16783
16784    let fs = FakeFs::new(cx.executor());
16785    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16786    let (editor, cx) = cx
16787        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16788    editor.update_in(cx, |editor, _window, cx| {
16789        for (buffer, diff_base) in [
16790            (buffer_1.clone(), base_text_1),
16791            (buffer_2.clone(), base_text_2),
16792            (buffer_3.clone(), base_text_3),
16793        ] {
16794            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16795            editor
16796                .buffer
16797                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16798        }
16799    });
16800    cx.executor().run_until_parked();
16801
16802    editor.update_in(cx, |editor, window, cx| {
16803        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}");
16804        editor.select_all(&SelectAll, window, cx);
16805        editor.git_restore(&Default::default(), window, cx);
16806    });
16807    cx.executor().run_until_parked();
16808
16809    // When all ranges are selected, all buffer hunks are reverted.
16810    editor.update(cx, |editor, cx| {
16811        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");
16812    });
16813    buffer_1.update(cx, |buffer, _| {
16814        assert_eq!(buffer.text(), base_text_1);
16815    });
16816    buffer_2.update(cx, |buffer, _| {
16817        assert_eq!(buffer.text(), base_text_2);
16818    });
16819    buffer_3.update(cx, |buffer, _| {
16820        assert_eq!(buffer.text(), base_text_3);
16821    });
16822
16823    editor.update_in(cx, |editor, window, cx| {
16824        editor.undo(&Default::default(), window, cx);
16825    });
16826
16827    editor.update_in(cx, |editor, window, cx| {
16828        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16829            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16830        });
16831        editor.git_restore(&Default::default(), window, cx);
16832    });
16833
16834    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16835    // but not affect buffer_2 and its related excerpts.
16836    editor.update(cx, |editor, cx| {
16837        assert_eq!(
16838            editor.text(cx),
16839            "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}"
16840        );
16841    });
16842    buffer_1.update(cx, |buffer, _| {
16843        assert_eq!(buffer.text(), base_text_1);
16844    });
16845    buffer_2.update(cx, |buffer, _| {
16846        assert_eq!(
16847            buffer.text(),
16848            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16849        );
16850    });
16851    buffer_3.update(cx, |buffer, _| {
16852        assert_eq!(
16853            buffer.text(),
16854            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16855        );
16856    });
16857
16858    fn edit_first_char_of_every_line(text: &str) -> String {
16859        text.split('\n')
16860            .map(|line| format!("X{}", &line[1..]))
16861            .collect::<Vec<_>>()
16862            .join("\n")
16863    }
16864}
16865
16866#[gpui::test]
16867async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16868    init_test(cx, |_| {});
16869
16870    let cols = 4;
16871    let rows = 10;
16872    let sample_text_1 = sample_text(rows, cols, 'a');
16873    assert_eq!(
16874        sample_text_1,
16875        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16876    );
16877    let sample_text_2 = sample_text(rows, cols, 'l');
16878    assert_eq!(
16879        sample_text_2,
16880        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16881    );
16882    let sample_text_3 = sample_text(rows, cols, 'v');
16883    assert_eq!(
16884        sample_text_3,
16885        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16886    );
16887
16888    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16889    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16890    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16891
16892    let multi_buffer = cx.new(|cx| {
16893        let mut multibuffer = MultiBuffer::new(ReadWrite);
16894        multibuffer.push_excerpts(
16895            buffer_1.clone(),
16896            [
16897                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16898                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16899                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16900            ],
16901            cx,
16902        );
16903        multibuffer.push_excerpts(
16904            buffer_2.clone(),
16905            [
16906                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16907                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16908                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16909            ],
16910            cx,
16911        );
16912        multibuffer.push_excerpts(
16913            buffer_3.clone(),
16914            [
16915                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16916                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16917                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16918            ],
16919            cx,
16920        );
16921        multibuffer
16922    });
16923
16924    let fs = FakeFs::new(cx.executor());
16925    fs.insert_tree(
16926        "/a",
16927        json!({
16928            "main.rs": sample_text_1,
16929            "other.rs": sample_text_2,
16930            "lib.rs": sample_text_3,
16931        }),
16932    )
16933    .await;
16934    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16935    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16936    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16937    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16938        Editor::new(
16939            EditorMode::full(),
16940            multi_buffer,
16941            Some(project.clone()),
16942            window,
16943            cx,
16944        )
16945    });
16946    let multibuffer_item_id = workspace
16947        .update(cx, |workspace, window, cx| {
16948            assert!(
16949                workspace.active_item(cx).is_none(),
16950                "active item should be None before the first item is added"
16951            );
16952            workspace.add_item_to_active_pane(
16953                Box::new(multi_buffer_editor.clone()),
16954                None,
16955                true,
16956                window,
16957                cx,
16958            );
16959            let active_item = workspace
16960                .active_item(cx)
16961                .expect("should have an active item after adding the multi buffer");
16962            assert!(
16963                !active_item.is_singleton(cx),
16964                "A multi buffer was expected to active after adding"
16965            );
16966            active_item.item_id()
16967        })
16968        .unwrap();
16969    cx.executor().run_until_parked();
16970
16971    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16972        editor.change_selections(
16973            SelectionEffects::scroll(Autoscroll::Next),
16974            window,
16975            cx,
16976            |s| s.select_ranges(Some(1..2)),
16977        );
16978        editor.open_excerpts(&OpenExcerpts, window, cx);
16979    });
16980    cx.executor().run_until_parked();
16981    let first_item_id = workspace
16982        .update(cx, |workspace, window, cx| {
16983            let active_item = workspace
16984                .active_item(cx)
16985                .expect("should have an active item after navigating into the 1st buffer");
16986            let first_item_id = active_item.item_id();
16987            assert_ne!(
16988                first_item_id, multibuffer_item_id,
16989                "Should navigate into the 1st buffer and activate it"
16990            );
16991            assert!(
16992                active_item.is_singleton(cx),
16993                "New active item should be a singleton buffer"
16994            );
16995            assert_eq!(
16996                active_item
16997                    .act_as::<Editor>(cx)
16998                    .expect("should have navigated into an editor for the 1st buffer")
16999                    .read(cx)
17000                    .text(cx),
17001                sample_text_1
17002            );
17003
17004            workspace
17005                .go_back(workspace.active_pane().downgrade(), window, cx)
17006                .detach_and_log_err(cx);
17007
17008            first_item_id
17009        })
17010        .unwrap();
17011    cx.executor().run_until_parked();
17012    workspace
17013        .update(cx, |workspace, _, cx| {
17014            let active_item = workspace
17015                .active_item(cx)
17016                .expect("should have an active item after navigating back");
17017            assert_eq!(
17018                active_item.item_id(),
17019                multibuffer_item_id,
17020                "Should navigate back to the multi buffer"
17021            );
17022            assert!(!active_item.is_singleton(cx));
17023        })
17024        .unwrap();
17025
17026    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17027        editor.change_selections(
17028            SelectionEffects::scroll(Autoscroll::Next),
17029            window,
17030            cx,
17031            |s| s.select_ranges(Some(39..40)),
17032        );
17033        editor.open_excerpts(&OpenExcerpts, window, cx);
17034    });
17035    cx.executor().run_until_parked();
17036    let second_item_id = workspace
17037        .update(cx, |workspace, window, cx| {
17038            let active_item = workspace
17039                .active_item(cx)
17040                .expect("should have an active item after navigating into the 2nd buffer");
17041            let second_item_id = active_item.item_id();
17042            assert_ne!(
17043                second_item_id, multibuffer_item_id,
17044                "Should navigate away from the multibuffer"
17045            );
17046            assert_ne!(
17047                second_item_id, first_item_id,
17048                "Should navigate into the 2nd buffer and activate it"
17049            );
17050            assert!(
17051                active_item.is_singleton(cx),
17052                "New active item should be a singleton buffer"
17053            );
17054            assert_eq!(
17055                active_item
17056                    .act_as::<Editor>(cx)
17057                    .expect("should have navigated into an editor")
17058                    .read(cx)
17059                    .text(cx),
17060                sample_text_2
17061            );
17062
17063            workspace
17064                .go_back(workspace.active_pane().downgrade(), window, cx)
17065                .detach_and_log_err(cx);
17066
17067            second_item_id
17068        })
17069        .unwrap();
17070    cx.executor().run_until_parked();
17071    workspace
17072        .update(cx, |workspace, _, cx| {
17073            let active_item = workspace
17074                .active_item(cx)
17075                .expect("should have an active item after navigating back from the 2nd buffer");
17076            assert_eq!(
17077                active_item.item_id(),
17078                multibuffer_item_id,
17079                "Should navigate back from the 2nd buffer to the multi buffer"
17080            );
17081            assert!(!active_item.is_singleton(cx));
17082        })
17083        .unwrap();
17084
17085    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17086        editor.change_selections(
17087            SelectionEffects::scroll(Autoscroll::Next),
17088            window,
17089            cx,
17090            |s| s.select_ranges(Some(70..70)),
17091        );
17092        editor.open_excerpts(&OpenExcerpts, window, cx);
17093    });
17094    cx.executor().run_until_parked();
17095    workspace
17096        .update(cx, |workspace, window, cx| {
17097            let active_item = workspace
17098                .active_item(cx)
17099                .expect("should have an active item after navigating into the 3rd buffer");
17100            let third_item_id = active_item.item_id();
17101            assert_ne!(
17102                third_item_id, multibuffer_item_id,
17103                "Should navigate into the 3rd buffer and activate it"
17104            );
17105            assert_ne!(third_item_id, first_item_id);
17106            assert_ne!(third_item_id, second_item_id);
17107            assert!(
17108                active_item.is_singleton(cx),
17109                "New active item should be a singleton buffer"
17110            );
17111            assert_eq!(
17112                active_item
17113                    .act_as::<Editor>(cx)
17114                    .expect("should have navigated into an editor")
17115                    .read(cx)
17116                    .text(cx),
17117                sample_text_3
17118            );
17119
17120            workspace
17121                .go_back(workspace.active_pane().downgrade(), window, cx)
17122                .detach_and_log_err(cx);
17123        })
17124        .unwrap();
17125    cx.executor().run_until_parked();
17126    workspace
17127        .update(cx, |workspace, _, cx| {
17128            let active_item = workspace
17129                .active_item(cx)
17130                .expect("should have an active item after navigating back from the 3rd buffer");
17131            assert_eq!(
17132                active_item.item_id(),
17133                multibuffer_item_id,
17134                "Should navigate back from the 3rd buffer to the multi buffer"
17135            );
17136            assert!(!active_item.is_singleton(cx));
17137        })
17138        .unwrap();
17139}
17140
17141#[gpui::test]
17142async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17143    init_test(cx, |_| {});
17144
17145    let mut cx = EditorTestContext::new(cx).await;
17146
17147    let diff_base = r#"
17148        use some::mod;
17149
17150        const A: u32 = 42;
17151
17152        fn main() {
17153            println!("hello");
17154
17155            println!("world");
17156        }
17157        "#
17158    .unindent();
17159
17160    cx.set_state(
17161        &r#"
17162        use some::modified;
17163
17164        ˇ
17165        fn main() {
17166            println!("hello there");
17167
17168            println!("around the");
17169            println!("world");
17170        }
17171        "#
17172        .unindent(),
17173    );
17174
17175    cx.set_head_text(&diff_base);
17176    executor.run_until_parked();
17177
17178    cx.update_editor(|editor, window, cx| {
17179        editor.go_to_next_hunk(&GoToHunk, window, cx);
17180        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17181    });
17182    executor.run_until_parked();
17183    cx.assert_state_with_diff(
17184        r#"
17185          use some::modified;
17186
17187
17188          fn main() {
17189        -     println!("hello");
17190        + ˇ    println!("hello there");
17191
17192              println!("around the");
17193              println!("world");
17194          }
17195        "#
17196        .unindent(),
17197    );
17198
17199    cx.update_editor(|editor, window, cx| {
17200        for _ in 0..2 {
17201            editor.go_to_next_hunk(&GoToHunk, window, cx);
17202            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17203        }
17204    });
17205    executor.run_until_parked();
17206    cx.assert_state_with_diff(
17207        r#"
17208        - use some::mod;
17209        + ˇuse some::modified;
17210
17211
17212          fn main() {
17213        -     println!("hello");
17214        +     println!("hello there");
17215
17216        +     println!("around the");
17217              println!("world");
17218          }
17219        "#
17220        .unindent(),
17221    );
17222
17223    cx.update_editor(|editor, window, cx| {
17224        editor.go_to_next_hunk(&GoToHunk, window, cx);
17225        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17226    });
17227    executor.run_until_parked();
17228    cx.assert_state_with_diff(
17229        r#"
17230        - use some::mod;
17231        + use some::modified;
17232
17233        - const A: u32 = 42;
17234          ˇ
17235          fn main() {
17236        -     println!("hello");
17237        +     println!("hello there");
17238
17239        +     println!("around the");
17240              println!("world");
17241          }
17242        "#
17243        .unindent(),
17244    );
17245
17246    cx.update_editor(|editor, window, cx| {
17247        editor.cancel(&Cancel, window, cx);
17248    });
17249
17250    cx.assert_state_with_diff(
17251        r#"
17252          use some::modified;
17253
17254          ˇ
17255          fn main() {
17256              println!("hello there");
17257
17258              println!("around the");
17259              println!("world");
17260          }
17261        "#
17262        .unindent(),
17263    );
17264}
17265
17266#[gpui::test]
17267async fn test_diff_base_change_with_expanded_diff_hunks(
17268    executor: BackgroundExecutor,
17269    cx: &mut TestAppContext,
17270) {
17271    init_test(cx, |_| {});
17272
17273    let mut cx = EditorTestContext::new(cx).await;
17274
17275    let diff_base = r#"
17276        use some::mod1;
17277        use some::mod2;
17278
17279        const A: u32 = 42;
17280        const B: u32 = 42;
17281        const C: u32 = 42;
17282
17283        fn main() {
17284            println!("hello");
17285
17286            println!("world");
17287        }
17288        "#
17289    .unindent();
17290
17291    cx.set_state(
17292        &r#"
17293        use some::mod2;
17294
17295        const A: u32 = 42;
17296        const C: u32 = 42;
17297
17298        fn main(ˇ) {
17299            //println!("hello");
17300
17301            println!("world");
17302            //
17303            //
17304        }
17305        "#
17306        .unindent(),
17307    );
17308
17309    cx.set_head_text(&diff_base);
17310    executor.run_until_parked();
17311
17312    cx.update_editor(|editor, window, cx| {
17313        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17314    });
17315    executor.run_until_parked();
17316    cx.assert_state_with_diff(
17317        r#"
17318        - use some::mod1;
17319          use some::mod2;
17320
17321          const A: u32 = 42;
17322        - const B: u32 = 42;
17323          const C: u32 = 42;
17324
17325          fn main(ˇ) {
17326        -     println!("hello");
17327        +     //println!("hello");
17328
17329              println!("world");
17330        +     //
17331        +     //
17332          }
17333        "#
17334        .unindent(),
17335    );
17336
17337    cx.set_head_text("new diff base!");
17338    executor.run_until_parked();
17339    cx.assert_state_with_diff(
17340        r#"
17341        - new diff base!
17342        + use some::mod2;
17343        +
17344        + const A: u32 = 42;
17345        + const C: u32 = 42;
17346        +
17347        + fn main(ˇ) {
17348        +     //println!("hello");
17349        +
17350        +     println!("world");
17351        +     //
17352        +     //
17353        + }
17354        "#
17355        .unindent(),
17356    );
17357}
17358
17359#[gpui::test]
17360async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17361    init_test(cx, |_| {});
17362
17363    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17364    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17365    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17366    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17367    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17368    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17369
17370    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17371    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17372    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17373
17374    let multi_buffer = cx.new(|cx| {
17375        let mut multibuffer = MultiBuffer::new(ReadWrite);
17376        multibuffer.push_excerpts(
17377            buffer_1.clone(),
17378            [
17379                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17380                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17381                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17382            ],
17383            cx,
17384        );
17385        multibuffer.push_excerpts(
17386            buffer_2.clone(),
17387            [
17388                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17389                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17390                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17391            ],
17392            cx,
17393        );
17394        multibuffer.push_excerpts(
17395            buffer_3.clone(),
17396            [
17397                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17398                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17399                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17400            ],
17401            cx,
17402        );
17403        multibuffer
17404    });
17405
17406    let editor =
17407        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17408    editor
17409        .update(cx, |editor, _window, cx| {
17410            for (buffer, diff_base) in [
17411                (buffer_1.clone(), file_1_old),
17412                (buffer_2.clone(), file_2_old),
17413                (buffer_3.clone(), file_3_old),
17414            ] {
17415                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17416                editor
17417                    .buffer
17418                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17419            }
17420        })
17421        .unwrap();
17422
17423    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17424    cx.run_until_parked();
17425
17426    cx.assert_editor_state(
17427        &"
17428            ˇaaa
17429            ccc
17430            ddd
17431
17432            ggg
17433            hhh
17434
17435
17436            lll
17437            mmm
17438            NNN
17439
17440            qqq
17441            rrr
17442
17443            uuu
17444            111
17445            222
17446            333
17447
17448            666
17449            777
17450
17451            000
17452            !!!"
17453        .unindent(),
17454    );
17455
17456    cx.update_editor(|editor, window, cx| {
17457        editor.select_all(&SelectAll, window, cx);
17458        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17459    });
17460    cx.executor().run_until_parked();
17461
17462    cx.assert_state_with_diff(
17463        "
17464            «aaa
17465          - bbb
17466            ccc
17467            ddd
17468
17469            ggg
17470            hhh
17471
17472
17473            lll
17474            mmm
17475          - nnn
17476          + NNN
17477
17478            qqq
17479            rrr
17480
17481            uuu
17482            111
17483            222
17484            333
17485
17486          + 666
17487            777
17488
17489            000
17490            !!!ˇ»"
17491            .unindent(),
17492    );
17493}
17494
17495#[gpui::test]
17496async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17497    init_test(cx, |_| {});
17498
17499    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17500    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17501
17502    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17503    let multi_buffer = cx.new(|cx| {
17504        let mut multibuffer = MultiBuffer::new(ReadWrite);
17505        multibuffer.push_excerpts(
17506            buffer.clone(),
17507            [
17508                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17509                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17510                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17511            ],
17512            cx,
17513        );
17514        multibuffer
17515    });
17516
17517    let editor =
17518        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17519    editor
17520        .update(cx, |editor, _window, cx| {
17521            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17522            editor
17523                .buffer
17524                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17525        })
17526        .unwrap();
17527
17528    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17529    cx.run_until_parked();
17530
17531    cx.update_editor(|editor, window, cx| {
17532        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17533    });
17534    cx.executor().run_until_parked();
17535
17536    // When the start of a hunk coincides with the start of its excerpt,
17537    // the hunk is expanded. When the start of a a hunk is earlier than
17538    // the start of its excerpt, the hunk is not expanded.
17539    cx.assert_state_with_diff(
17540        "
17541            ˇaaa
17542          - bbb
17543          + BBB
17544
17545          - ddd
17546          - eee
17547          + DDD
17548          + EEE
17549            fff
17550
17551            iii
17552        "
17553        .unindent(),
17554    );
17555}
17556
17557#[gpui::test]
17558async fn test_edits_around_expanded_insertion_hunks(
17559    executor: BackgroundExecutor,
17560    cx: &mut TestAppContext,
17561) {
17562    init_test(cx, |_| {});
17563
17564    let mut cx = EditorTestContext::new(cx).await;
17565
17566    let diff_base = r#"
17567        use some::mod1;
17568        use some::mod2;
17569
17570        const A: u32 = 42;
17571
17572        fn main() {
17573            println!("hello");
17574
17575            println!("world");
17576        }
17577        "#
17578    .unindent();
17579    executor.run_until_parked();
17580    cx.set_state(
17581        &r#"
17582        use some::mod1;
17583        use some::mod2;
17584
17585        const A: u32 = 42;
17586        const B: u32 = 42;
17587        const C: u32 = 42;
17588        ˇ
17589
17590        fn main() {
17591            println!("hello");
17592
17593            println!("world");
17594        }
17595        "#
17596        .unindent(),
17597    );
17598
17599    cx.set_head_text(&diff_base);
17600    executor.run_until_parked();
17601
17602    cx.update_editor(|editor, window, cx| {
17603        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17604    });
17605    executor.run_until_parked();
17606
17607    cx.assert_state_with_diff(
17608        r#"
17609        use some::mod1;
17610        use some::mod2;
17611
17612        const A: u32 = 42;
17613      + const B: u32 = 42;
17614      + const C: u32 = 42;
17615      + ˇ
17616
17617        fn main() {
17618            println!("hello");
17619
17620            println!("world");
17621        }
17622      "#
17623        .unindent(),
17624    );
17625
17626    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17627    executor.run_until_parked();
17628
17629    cx.assert_state_with_diff(
17630        r#"
17631        use some::mod1;
17632        use some::mod2;
17633
17634        const A: u32 = 42;
17635      + const B: u32 = 42;
17636      + const C: u32 = 42;
17637      + const D: u32 = 42;
17638      + ˇ
17639
17640        fn main() {
17641            println!("hello");
17642
17643            println!("world");
17644        }
17645      "#
17646        .unindent(),
17647    );
17648
17649    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17650    executor.run_until_parked();
17651
17652    cx.assert_state_with_diff(
17653        r#"
17654        use some::mod1;
17655        use some::mod2;
17656
17657        const A: u32 = 42;
17658      + const B: u32 = 42;
17659      + const C: u32 = 42;
17660      + const D: u32 = 42;
17661      + const E: u32 = 42;
17662      + ˇ
17663
17664        fn main() {
17665            println!("hello");
17666
17667            println!("world");
17668        }
17669      "#
17670        .unindent(),
17671    );
17672
17673    cx.update_editor(|editor, window, cx| {
17674        editor.delete_line(&DeleteLine, window, cx);
17675    });
17676    executor.run_until_parked();
17677
17678    cx.assert_state_with_diff(
17679        r#"
17680        use some::mod1;
17681        use some::mod2;
17682
17683        const A: u32 = 42;
17684      + const B: u32 = 42;
17685      + const C: u32 = 42;
17686      + const D: u32 = 42;
17687      + const E: u32 = 42;
17688        ˇ
17689        fn main() {
17690            println!("hello");
17691
17692            println!("world");
17693        }
17694      "#
17695        .unindent(),
17696    );
17697
17698    cx.update_editor(|editor, window, cx| {
17699        editor.move_up(&MoveUp, window, cx);
17700        editor.delete_line(&DeleteLine, window, cx);
17701        editor.move_up(&MoveUp, window, cx);
17702        editor.delete_line(&DeleteLine, window, cx);
17703        editor.move_up(&MoveUp, window, cx);
17704        editor.delete_line(&DeleteLine, window, cx);
17705    });
17706    executor.run_until_parked();
17707    cx.assert_state_with_diff(
17708        r#"
17709        use some::mod1;
17710        use some::mod2;
17711
17712        const A: u32 = 42;
17713      + const B: u32 = 42;
17714        ˇ
17715        fn main() {
17716            println!("hello");
17717
17718            println!("world");
17719        }
17720      "#
17721        .unindent(),
17722    );
17723
17724    cx.update_editor(|editor, window, cx| {
17725        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17726        editor.delete_line(&DeleteLine, window, cx);
17727    });
17728    executor.run_until_parked();
17729    cx.assert_state_with_diff(
17730        r#"
17731        ˇ
17732        fn main() {
17733            println!("hello");
17734
17735            println!("world");
17736        }
17737      "#
17738        .unindent(),
17739    );
17740}
17741
17742#[gpui::test]
17743async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17744    init_test(cx, |_| {});
17745
17746    let mut cx = EditorTestContext::new(cx).await;
17747    cx.set_head_text(indoc! { "
17748        one
17749        two
17750        three
17751        four
17752        five
17753        "
17754    });
17755    cx.set_state(indoc! { "
17756        one
17757        ˇthree
17758        five
17759    "});
17760    cx.run_until_parked();
17761    cx.update_editor(|editor, window, cx| {
17762        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17763    });
17764    cx.assert_state_with_diff(
17765        indoc! { "
17766        one
17767      - two
17768        ˇthree
17769      - four
17770        five
17771    "}
17772        .to_string(),
17773    );
17774    cx.update_editor(|editor, window, cx| {
17775        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17776    });
17777
17778    cx.assert_state_with_diff(
17779        indoc! { "
17780        one
17781        ˇthree
17782        five
17783    "}
17784        .to_string(),
17785    );
17786
17787    cx.set_state(indoc! { "
17788        one
17789        ˇTWO
17790        three
17791        four
17792        five
17793    "});
17794    cx.run_until_parked();
17795    cx.update_editor(|editor, window, cx| {
17796        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17797    });
17798
17799    cx.assert_state_with_diff(
17800        indoc! { "
17801            one
17802          - two
17803          + ˇTWO
17804            three
17805            four
17806            five
17807        "}
17808        .to_string(),
17809    );
17810    cx.update_editor(|editor, window, cx| {
17811        editor.move_up(&Default::default(), window, cx);
17812        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17813    });
17814    cx.assert_state_with_diff(
17815        indoc! { "
17816            one
17817            ˇTWO
17818            three
17819            four
17820            five
17821        "}
17822        .to_string(),
17823    );
17824}
17825
17826#[gpui::test]
17827async fn test_edits_around_expanded_deletion_hunks(
17828    executor: BackgroundExecutor,
17829    cx: &mut TestAppContext,
17830) {
17831    init_test(cx, |_| {});
17832
17833    let mut cx = EditorTestContext::new(cx).await;
17834
17835    let diff_base = r#"
17836        use some::mod1;
17837        use some::mod2;
17838
17839        const A: u32 = 42;
17840        const B: u32 = 42;
17841        const C: u32 = 42;
17842
17843
17844        fn main() {
17845            println!("hello");
17846
17847            println!("world");
17848        }
17849    "#
17850    .unindent();
17851    executor.run_until_parked();
17852    cx.set_state(
17853        &r#"
17854        use some::mod1;
17855        use some::mod2;
17856
17857        ˇconst B: u32 = 42;
17858        const C: u32 = 42;
17859
17860
17861        fn main() {
17862            println!("hello");
17863
17864            println!("world");
17865        }
17866        "#
17867        .unindent(),
17868    );
17869
17870    cx.set_head_text(&diff_base);
17871    executor.run_until_parked();
17872
17873    cx.update_editor(|editor, window, cx| {
17874        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17875    });
17876    executor.run_until_parked();
17877
17878    cx.assert_state_with_diff(
17879        r#"
17880        use some::mod1;
17881        use some::mod2;
17882
17883      - const A: u32 = 42;
17884        ˇconst B: u32 = 42;
17885        const C: u32 = 42;
17886
17887
17888        fn main() {
17889            println!("hello");
17890
17891            println!("world");
17892        }
17893      "#
17894        .unindent(),
17895    );
17896
17897    cx.update_editor(|editor, window, cx| {
17898        editor.delete_line(&DeleteLine, window, cx);
17899    });
17900    executor.run_until_parked();
17901    cx.assert_state_with_diff(
17902        r#"
17903        use some::mod1;
17904        use some::mod2;
17905
17906      - const A: u32 = 42;
17907      - const B: u32 = 42;
17908        ˇconst C: u32 = 42;
17909
17910
17911        fn main() {
17912            println!("hello");
17913
17914            println!("world");
17915        }
17916      "#
17917        .unindent(),
17918    );
17919
17920    cx.update_editor(|editor, window, cx| {
17921        editor.delete_line(&DeleteLine, window, cx);
17922    });
17923    executor.run_until_parked();
17924    cx.assert_state_with_diff(
17925        r#"
17926        use some::mod1;
17927        use some::mod2;
17928
17929      - const A: u32 = 42;
17930      - const B: u32 = 42;
17931      - const C: u32 = 42;
17932        ˇ
17933
17934        fn main() {
17935            println!("hello");
17936
17937            println!("world");
17938        }
17939      "#
17940        .unindent(),
17941    );
17942
17943    cx.update_editor(|editor, window, cx| {
17944        editor.handle_input("replacement", window, cx);
17945    });
17946    executor.run_until_parked();
17947    cx.assert_state_with_diff(
17948        r#"
17949        use some::mod1;
17950        use some::mod2;
17951
17952      - const A: u32 = 42;
17953      - const B: u32 = 42;
17954      - const C: u32 = 42;
17955      -
17956      + replacementˇ
17957
17958        fn main() {
17959            println!("hello");
17960
17961            println!("world");
17962        }
17963      "#
17964        .unindent(),
17965    );
17966}
17967
17968#[gpui::test]
17969async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17970    init_test(cx, |_| {});
17971
17972    let mut cx = EditorTestContext::new(cx).await;
17973
17974    let base_text = r#"
17975        one
17976        two
17977        three
17978        four
17979        five
17980    "#
17981    .unindent();
17982    executor.run_until_parked();
17983    cx.set_state(
17984        &r#"
17985        one
17986        two
17987        fˇour
17988        five
17989        "#
17990        .unindent(),
17991    );
17992
17993    cx.set_head_text(&base_text);
17994    executor.run_until_parked();
17995
17996    cx.update_editor(|editor, window, cx| {
17997        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17998    });
17999    executor.run_until_parked();
18000
18001    cx.assert_state_with_diff(
18002        r#"
18003          one
18004          two
18005        - three
18006          fˇour
18007          five
18008        "#
18009        .unindent(),
18010    );
18011
18012    cx.update_editor(|editor, window, cx| {
18013        editor.backspace(&Backspace, window, cx);
18014        editor.backspace(&Backspace, window, cx);
18015    });
18016    executor.run_until_parked();
18017    cx.assert_state_with_diff(
18018        r#"
18019          one
18020          two
18021        - threeˇ
18022        - four
18023        + our
18024          five
18025        "#
18026        .unindent(),
18027    );
18028}
18029
18030#[gpui::test]
18031async fn test_edit_after_expanded_modification_hunk(
18032    executor: BackgroundExecutor,
18033    cx: &mut TestAppContext,
18034) {
18035    init_test(cx, |_| {});
18036
18037    let mut cx = EditorTestContext::new(cx).await;
18038
18039    let diff_base = r#"
18040        use some::mod1;
18041        use some::mod2;
18042
18043        const A: u32 = 42;
18044        const B: u32 = 42;
18045        const C: u32 = 42;
18046        const D: u32 = 42;
18047
18048
18049        fn main() {
18050            println!("hello");
18051
18052            println!("world");
18053        }"#
18054    .unindent();
18055
18056    cx.set_state(
18057        &r#"
18058        use some::mod1;
18059        use some::mod2;
18060
18061        const A: u32 = 42;
18062        const B: u32 = 42;
18063        const C: u32 = 43ˇ
18064        const D: u32 = 42;
18065
18066
18067        fn main() {
18068            println!("hello");
18069
18070            println!("world");
18071        }"#
18072        .unindent(),
18073    );
18074
18075    cx.set_head_text(&diff_base);
18076    executor.run_until_parked();
18077    cx.update_editor(|editor, window, cx| {
18078        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18079    });
18080    executor.run_until_parked();
18081
18082    cx.assert_state_with_diff(
18083        r#"
18084        use some::mod1;
18085        use some::mod2;
18086
18087        const A: u32 = 42;
18088        const B: u32 = 42;
18089      - const C: u32 = 42;
18090      + const C: u32 = 43ˇ
18091        const D: u32 = 42;
18092
18093
18094        fn main() {
18095            println!("hello");
18096
18097            println!("world");
18098        }"#
18099        .unindent(),
18100    );
18101
18102    cx.update_editor(|editor, window, cx| {
18103        editor.handle_input("\nnew_line\n", window, cx);
18104    });
18105    executor.run_until_parked();
18106
18107    cx.assert_state_with_diff(
18108        r#"
18109        use some::mod1;
18110        use some::mod2;
18111
18112        const A: u32 = 42;
18113        const B: u32 = 42;
18114      - const C: u32 = 42;
18115      + const C: u32 = 43
18116      + new_line
18117      + ˇ
18118        const D: u32 = 42;
18119
18120
18121        fn main() {
18122            println!("hello");
18123
18124            println!("world");
18125        }"#
18126        .unindent(),
18127    );
18128}
18129
18130#[gpui::test]
18131async fn test_stage_and_unstage_added_file_hunk(
18132    executor: BackgroundExecutor,
18133    cx: &mut TestAppContext,
18134) {
18135    init_test(cx, |_| {});
18136
18137    let mut cx = EditorTestContext::new(cx).await;
18138    cx.update_editor(|editor, _, cx| {
18139        editor.set_expand_all_diff_hunks(cx);
18140    });
18141
18142    let working_copy = r#"
18143            ˇfn main() {
18144                println!("hello, world!");
18145            }
18146        "#
18147    .unindent();
18148
18149    cx.set_state(&working_copy);
18150    executor.run_until_parked();
18151
18152    cx.assert_state_with_diff(
18153        r#"
18154            + ˇfn main() {
18155            +     println!("hello, world!");
18156            + }
18157        "#
18158        .unindent(),
18159    );
18160    cx.assert_index_text(None);
18161
18162    cx.update_editor(|editor, window, cx| {
18163        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18164    });
18165    executor.run_until_parked();
18166    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18167    cx.assert_state_with_diff(
18168        r#"
18169            + ˇfn main() {
18170            +     println!("hello, world!");
18171            + }
18172        "#
18173        .unindent(),
18174    );
18175
18176    cx.update_editor(|editor, window, cx| {
18177        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18178    });
18179    executor.run_until_parked();
18180    cx.assert_index_text(None);
18181}
18182
18183async fn setup_indent_guides_editor(
18184    text: &str,
18185    cx: &mut TestAppContext,
18186) -> (BufferId, EditorTestContext) {
18187    init_test(cx, |_| {});
18188
18189    let mut cx = EditorTestContext::new(cx).await;
18190
18191    let buffer_id = cx.update_editor(|editor, window, cx| {
18192        editor.set_text(text, window, cx);
18193        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18194
18195        buffer_ids[0]
18196    });
18197
18198    (buffer_id, cx)
18199}
18200
18201fn assert_indent_guides(
18202    range: Range<u32>,
18203    expected: Vec<IndentGuide>,
18204    active_indices: Option<Vec<usize>>,
18205    cx: &mut EditorTestContext,
18206) {
18207    let indent_guides = cx.update_editor(|editor, window, cx| {
18208        let snapshot = editor.snapshot(window, cx).display_snapshot;
18209        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18210            editor,
18211            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18212            true,
18213            &snapshot,
18214            cx,
18215        );
18216
18217        indent_guides.sort_by(|a, b| {
18218            a.depth.cmp(&b.depth).then(
18219                a.start_row
18220                    .cmp(&b.start_row)
18221                    .then(a.end_row.cmp(&b.end_row)),
18222            )
18223        });
18224        indent_guides
18225    });
18226
18227    if let Some(expected) = active_indices {
18228        let active_indices = cx.update_editor(|editor, window, cx| {
18229            let snapshot = editor.snapshot(window, cx).display_snapshot;
18230            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18231        });
18232
18233        assert_eq!(
18234            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18235            expected,
18236            "Active indent guide indices do not match"
18237        );
18238    }
18239
18240    assert_eq!(indent_guides, expected, "Indent guides do not match");
18241}
18242
18243fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18244    IndentGuide {
18245        buffer_id,
18246        start_row: MultiBufferRow(start_row),
18247        end_row: MultiBufferRow(end_row),
18248        depth,
18249        tab_size: 4,
18250        settings: IndentGuideSettings {
18251            enabled: true,
18252            line_width: 1,
18253            active_line_width: 1,
18254            ..Default::default()
18255        },
18256    }
18257}
18258
18259#[gpui::test]
18260async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18261    let (buffer_id, mut cx) = setup_indent_guides_editor(
18262        &"
18263        fn main() {
18264            let a = 1;
18265        }"
18266        .unindent(),
18267        cx,
18268    )
18269    .await;
18270
18271    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18272}
18273
18274#[gpui::test]
18275async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18276    let (buffer_id, mut cx) = setup_indent_guides_editor(
18277        &"
18278        fn main() {
18279            let a = 1;
18280            let b = 2;
18281        }"
18282        .unindent(),
18283        cx,
18284    )
18285    .await;
18286
18287    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18288}
18289
18290#[gpui::test]
18291async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18292    let (buffer_id, mut cx) = setup_indent_guides_editor(
18293        &"
18294        fn main() {
18295            let a = 1;
18296            if a == 3 {
18297                let b = 2;
18298            } else {
18299                let c = 3;
18300            }
18301        }"
18302        .unindent(),
18303        cx,
18304    )
18305    .await;
18306
18307    assert_indent_guides(
18308        0..8,
18309        vec![
18310            indent_guide(buffer_id, 1, 6, 0),
18311            indent_guide(buffer_id, 3, 3, 1),
18312            indent_guide(buffer_id, 5, 5, 1),
18313        ],
18314        None,
18315        &mut cx,
18316    );
18317}
18318
18319#[gpui::test]
18320async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18321    let (buffer_id, mut cx) = setup_indent_guides_editor(
18322        &"
18323        fn main() {
18324            let a = 1;
18325                let b = 2;
18326            let c = 3;
18327        }"
18328        .unindent(),
18329        cx,
18330    )
18331    .await;
18332
18333    assert_indent_guides(
18334        0..5,
18335        vec![
18336            indent_guide(buffer_id, 1, 3, 0),
18337            indent_guide(buffer_id, 2, 2, 1),
18338        ],
18339        None,
18340        &mut cx,
18341    );
18342}
18343
18344#[gpui::test]
18345async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18346    let (buffer_id, mut cx) = setup_indent_guides_editor(
18347        &"
18348        fn main() {
18349            let a = 1;
18350
18351            let c = 3;
18352        }"
18353        .unindent(),
18354        cx,
18355    )
18356    .await;
18357
18358    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18359}
18360
18361#[gpui::test]
18362async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18363    let (buffer_id, mut cx) = setup_indent_guides_editor(
18364        &"
18365        fn main() {
18366            let a = 1;
18367
18368            let c = 3;
18369
18370            if a == 3 {
18371                let b = 2;
18372            } else {
18373                let c = 3;
18374            }
18375        }"
18376        .unindent(),
18377        cx,
18378    )
18379    .await;
18380
18381    assert_indent_guides(
18382        0..11,
18383        vec![
18384            indent_guide(buffer_id, 1, 9, 0),
18385            indent_guide(buffer_id, 6, 6, 1),
18386            indent_guide(buffer_id, 8, 8, 1),
18387        ],
18388        None,
18389        &mut cx,
18390    );
18391}
18392
18393#[gpui::test]
18394async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18395    let (buffer_id, mut cx) = setup_indent_guides_editor(
18396        &"
18397        fn main() {
18398            let a = 1;
18399
18400            let c = 3;
18401
18402            if a == 3 {
18403                let b = 2;
18404            } else {
18405                let c = 3;
18406            }
18407        }"
18408        .unindent(),
18409        cx,
18410    )
18411    .await;
18412
18413    assert_indent_guides(
18414        1..11,
18415        vec![
18416            indent_guide(buffer_id, 1, 9, 0),
18417            indent_guide(buffer_id, 6, 6, 1),
18418            indent_guide(buffer_id, 8, 8, 1),
18419        ],
18420        None,
18421        &mut cx,
18422    );
18423}
18424
18425#[gpui::test]
18426async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18427    let (buffer_id, mut cx) = setup_indent_guides_editor(
18428        &"
18429        fn main() {
18430            let a = 1;
18431
18432            let c = 3;
18433
18434            if a == 3 {
18435                let b = 2;
18436            } else {
18437                let c = 3;
18438            }
18439        }"
18440        .unindent(),
18441        cx,
18442    )
18443    .await;
18444
18445    assert_indent_guides(
18446        1..10,
18447        vec![
18448            indent_guide(buffer_id, 1, 9, 0),
18449            indent_guide(buffer_id, 6, 6, 1),
18450            indent_guide(buffer_id, 8, 8, 1),
18451        ],
18452        None,
18453        &mut cx,
18454    );
18455}
18456
18457#[gpui::test]
18458async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18459    let (buffer_id, mut cx) = setup_indent_guides_editor(
18460        &"
18461        fn main() {
18462            if a {
18463                b(
18464                    c,
18465                    d,
18466                )
18467            } else {
18468                e(
18469                    f
18470                )
18471            }
18472        }"
18473        .unindent(),
18474        cx,
18475    )
18476    .await;
18477
18478    assert_indent_guides(
18479        0..11,
18480        vec![
18481            indent_guide(buffer_id, 1, 10, 0),
18482            indent_guide(buffer_id, 2, 5, 1),
18483            indent_guide(buffer_id, 7, 9, 1),
18484            indent_guide(buffer_id, 3, 4, 2),
18485            indent_guide(buffer_id, 8, 8, 2),
18486        ],
18487        None,
18488        &mut cx,
18489    );
18490
18491    cx.update_editor(|editor, window, cx| {
18492        editor.fold_at(MultiBufferRow(2), window, cx);
18493        assert_eq!(
18494            editor.display_text(cx),
18495            "
18496            fn main() {
18497                if a {
18498                    b(⋯
18499                    )
18500                } else {
18501                    e(
18502                        f
18503                    )
18504                }
18505            }"
18506            .unindent()
18507        );
18508    });
18509
18510    assert_indent_guides(
18511        0..11,
18512        vec![
18513            indent_guide(buffer_id, 1, 10, 0),
18514            indent_guide(buffer_id, 2, 5, 1),
18515            indent_guide(buffer_id, 7, 9, 1),
18516            indent_guide(buffer_id, 8, 8, 2),
18517        ],
18518        None,
18519        &mut cx,
18520    );
18521}
18522
18523#[gpui::test]
18524async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18525    let (buffer_id, mut cx) = setup_indent_guides_editor(
18526        &"
18527        block1
18528            block2
18529                block3
18530                    block4
18531            block2
18532        block1
18533        block1"
18534            .unindent(),
18535        cx,
18536    )
18537    .await;
18538
18539    assert_indent_guides(
18540        1..10,
18541        vec![
18542            indent_guide(buffer_id, 1, 4, 0),
18543            indent_guide(buffer_id, 2, 3, 1),
18544            indent_guide(buffer_id, 3, 3, 2),
18545        ],
18546        None,
18547        &mut cx,
18548    );
18549}
18550
18551#[gpui::test]
18552async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18553    let (buffer_id, mut cx) = setup_indent_guides_editor(
18554        &"
18555        block1
18556            block2
18557                block3
18558
18559        block1
18560        block1"
18561            .unindent(),
18562        cx,
18563    )
18564    .await;
18565
18566    assert_indent_guides(
18567        0..6,
18568        vec![
18569            indent_guide(buffer_id, 1, 2, 0),
18570            indent_guide(buffer_id, 2, 2, 1),
18571        ],
18572        None,
18573        &mut cx,
18574    );
18575}
18576
18577#[gpui::test]
18578async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18579    let (buffer_id, mut cx) = setup_indent_guides_editor(
18580        &"
18581        function component() {
18582        \treturn (
18583        \t\t\t
18584        \t\t<div>
18585        \t\t\t<abc></abc>
18586        \t\t</div>
18587        \t)
18588        }"
18589        .unindent(),
18590        cx,
18591    )
18592    .await;
18593
18594    assert_indent_guides(
18595        0..8,
18596        vec![
18597            indent_guide(buffer_id, 1, 6, 0),
18598            indent_guide(buffer_id, 2, 5, 1),
18599            indent_guide(buffer_id, 4, 4, 2),
18600        ],
18601        None,
18602        &mut cx,
18603    );
18604}
18605
18606#[gpui::test]
18607async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18608    let (buffer_id, mut cx) = setup_indent_guides_editor(
18609        &"
18610        function component() {
18611        \treturn (
18612        \t
18613        \t\t<div>
18614        \t\t\t<abc></abc>
18615        \t\t</div>
18616        \t)
18617        }"
18618        .unindent(),
18619        cx,
18620    )
18621    .await;
18622
18623    assert_indent_guides(
18624        0..8,
18625        vec![
18626            indent_guide(buffer_id, 1, 6, 0),
18627            indent_guide(buffer_id, 2, 5, 1),
18628            indent_guide(buffer_id, 4, 4, 2),
18629        ],
18630        None,
18631        &mut cx,
18632    );
18633}
18634
18635#[gpui::test]
18636async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18637    let (buffer_id, mut cx) = setup_indent_guides_editor(
18638        &"
18639        block1
18640
18641
18642
18643            block2
18644        "
18645        .unindent(),
18646        cx,
18647    )
18648    .await;
18649
18650    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18651}
18652
18653#[gpui::test]
18654async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18655    let (buffer_id, mut cx) = setup_indent_guides_editor(
18656        &"
18657        def a:
18658        \tb = 3
18659        \tif True:
18660        \t\tc = 4
18661        \t\td = 5
18662        \tprint(b)
18663        "
18664        .unindent(),
18665        cx,
18666    )
18667    .await;
18668
18669    assert_indent_guides(
18670        0..6,
18671        vec![
18672            indent_guide(buffer_id, 1, 5, 0),
18673            indent_guide(buffer_id, 3, 4, 1),
18674        ],
18675        None,
18676        &mut cx,
18677    );
18678}
18679
18680#[gpui::test]
18681async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18682    let (buffer_id, mut cx) = setup_indent_guides_editor(
18683        &"
18684    fn main() {
18685        let a = 1;
18686    }"
18687        .unindent(),
18688        cx,
18689    )
18690    .await;
18691
18692    cx.update_editor(|editor, window, cx| {
18693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18694            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18695        });
18696    });
18697
18698    assert_indent_guides(
18699        0..3,
18700        vec![indent_guide(buffer_id, 1, 1, 0)],
18701        Some(vec![0]),
18702        &mut cx,
18703    );
18704}
18705
18706#[gpui::test]
18707async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18708    let (buffer_id, mut cx) = setup_indent_guides_editor(
18709        &"
18710    fn main() {
18711        if 1 == 2 {
18712            let a = 1;
18713        }
18714    }"
18715        .unindent(),
18716        cx,
18717    )
18718    .await;
18719
18720    cx.update_editor(|editor, window, cx| {
18721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18722            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18723        });
18724    });
18725
18726    assert_indent_guides(
18727        0..4,
18728        vec![
18729            indent_guide(buffer_id, 1, 3, 0),
18730            indent_guide(buffer_id, 2, 2, 1),
18731        ],
18732        Some(vec![1]),
18733        &mut cx,
18734    );
18735
18736    cx.update_editor(|editor, window, cx| {
18737        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18738            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18739        });
18740    });
18741
18742    assert_indent_guides(
18743        0..4,
18744        vec![
18745            indent_guide(buffer_id, 1, 3, 0),
18746            indent_guide(buffer_id, 2, 2, 1),
18747        ],
18748        Some(vec![1]),
18749        &mut cx,
18750    );
18751
18752    cx.update_editor(|editor, window, cx| {
18753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18754            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18755        });
18756    });
18757
18758    assert_indent_guides(
18759        0..4,
18760        vec![
18761            indent_guide(buffer_id, 1, 3, 0),
18762            indent_guide(buffer_id, 2, 2, 1),
18763        ],
18764        Some(vec![0]),
18765        &mut cx,
18766    );
18767}
18768
18769#[gpui::test]
18770async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18771    let (buffer_id, mut cx) = setup_indent_guides_editor(
18772        &"
18773    fn main() {
18774        let a = 1;
18775
18776        let b = 2;
18777    }"
18778        .unindent(),
18779        cx,
18780    )
18781    .await;
18782
18783    cx.update_editor(|editor, window, cx| {
18784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18785            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18786        });
18787    });
18788
18789    assert_indent_guides(
18790        0..5,
18791        vec![indent_guide(buffer_id, 1, 3, 0)],
18792        Some(vec![0]),
18793        &mut cx,
18794    );
18795}
18796
18797#[gpui::test]
18798async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18799    let (buffer_id, mut cx) = setup_indent_guides_editor(
18800        &"
18801    def m:
18802        a = 1
18803        pass"
18804            .unindent(),
18805        cx,
18806    )
18807    .await;
18808
18809    cx.update_editor(|editor, window, cx| {
18810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18811            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18812        });
18813    });
18814
18815    assert_indent_guides(
18816        0..3,
18817        vec![indent_guide(buffer_id, 1, 2, 0)],
18818        Some(vec![0]),
18819        &mut cx,
18820    );
18821}
18822
18823#[gpui::test]
18824async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18825    init_test(cx, |_| {});
18826    let mut cx = EditorTestContext::new(cx).await;
18827    let text = indoc! {
18828        "
18829        impl A {
18830            fn b() {
18831                0;
18832                3;
18833                5;
18834                6;
18835                7;
18836            }
18837        }
18838        "
18839    };
18840    let base_text = indoc! {
18841        "
18842        impl A {
18843            fn b() {
18844                0;
18845                1;
18846                2;
18847                3;
18848                4;
18849            }
18850            fn c() {
18851                5;
18852                6;
18853                7;
18854            }
18855        }
18856        "
18857    };
18858
18859    cx.update_editor(|editor, window, cx| {
18860        editor.set_text(text, window, cx);
18861
18862        editor.buffer().update(cx, |multibuffer, cx| {
18863            let buffer = multibuffer.as_singleton().unwrap();
18864            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18865
18866            multibuffer.set_all_diff_hunks_expanded(cx);
18867            multibuffer.add_diff(diff, cx);
18868
18869            buffer.read(cx).remote_id()
18870        })
18871    });
18872    cx.run_until_parked();
18873
18874    cx.assert_state_with_diff(
18875        indoc! { "
18876          impl A {
18877              fn b() {
18878                  0;
18879        -         1;
18880        -         2;
18881                  3;
18882        -         4;
18883        -     }
18884        -     fn c() {
18885                  5;
18886                  6;
18887                  7;
18888              }
18889          }
18890          ˇ"
18891        }
18892        .to_string(),
18893    );
18894
18895    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18896        editor
18897            .snapshot(window, cx)
18898            .buffer_snapshot
18899            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18900            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18901            .collect::<Vec<_>>()
18902    });
18903    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18904    assert_eq!(
18905        actual_guides,
18906        vec![
18907            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18908            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18909            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18910        ]
18911    );
18912}
18913
18914#[gpui::test]
18915async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18916    init_test(cx, |_| {});
18917    let mut cx = EditorTestContext::new(cx).await;
18918
18919    let diff_base = r#"
18920        a
18921        b
18922        c
18923        "#
18924    .unindent();
18925
18926    cx.set_state(
18927        &r#"
18928        ˇA
18929        b
18930        C
18931        "#
18932        .unindent(),
18933    );
18934    cx.set_head_text(&diff_base);
18935    cx.update_editor(|editor, window, cx| {
18936        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18937    });
18938    executor.run_until_parked();
18939
18940    let both_hunks_expanded = r#"
18941        - a
18942        + ˇA
18943          b
18944        - c
18945        + C
18946        "#
18947    .unindent();
18948
18949    cx.assert_state_with_diff(both_hunks_expanded.clone());
18950
18951    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18952        let snapshot = editor.snapshot(window, cx);
18953        let hunks = editor
18954            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18955            .collect::<Vec<_>>();
18956        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18957        let buffer_id = hunks[0].buffer_id;
18958        hunks
18959            .into_iter()
18960            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18961            .collect::<Vec<_>>()
18962    });
18963    assert_eq!(hunk_ranges.len(), 2);
18964
18965    cx.update_editor(|editor, _, cx| {
18966        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18967    });
18968    executor.run_until_parked();
18969
18970    let second_hunk_expanded = r#"
18971          ˇA
18972          b
18973        - c
18974        + C
18975        "#
18976    .unindent();
18977
18978    cx.assert_state_with_diff(second_hunk_expanded);
18979
18980    cx.update_editor(|editor, _, cx| {
18981        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18982    });
18983    executor.run_until_parked();
18984
18985    cx.assert_state_with_diff(both_hunks_expanded.clone());
18986
18987    cx.update_editor(|editor, _, cx| {
18988        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18989    });
18990    executor.run_until_parked();
18991
18992    let first_hunk_expanded = r#"
18993        - a
18994        + ˇA
18995          b
18996          C
18997        "#
18998    .unindent();
18999
19000    cx.assert_state_with_diff(first_hunk_expanded);
19001
19002    cx.update_editor(|editor, _, cx| {
19003        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19004    });
19005    executor.run_until_parked();
19006
19007    cx.assert_state_with_diff(both_hunks_expanded);
19008
19009    cx.set_state(
19010        &r#"
19011        ˇA
19012        b
19013        "#
19014        .unindent(),
19015    );
19016    cx.run_until_parked();
19017
19018    // TODO this cursor position seems bad
19019    cx.assert_state_with_diff(
19020        r#"
19021        - ˇa
19022        + A
19023          b
19024        "#
19025        .unindent(),
19026    );
19027
19028    cx.update_editor(|editor, window, cx| {
19029        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19030    });
19031
19032    cx.assert_state_with_diff(
19033        r#"
19034            - ˇa
19035            + A
19036              b
19037            - c
19038            "#
19039        .unindent(),
19040    );
19041
19042    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19043        let snapshot = editor.snapshot(window, cx);
19044        let hunks = editor
19045            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19046            .collect::<Vec<_>>();
19047        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19048        let buffer_id = hunks[0].buffer_id;
19049        hunks
19050            .into_iter()
19051            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19052            .collect::<Vec<_>>()
19053    });
19054    assert_eq!(hunk_ranges.len(), 2);
19055
19056    cx.update_editor(|editor, _, cx| {
19057        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19058    });
19059    executor.run_until_parked();
19060
19061    cx.assert_state_with_diff(
19062        r#"
19063        - ˇa
19064        + A
19065          b
19066        "#
19067        .unindent(),
19068    );
19069}
19070
19071#[gpui::test]
19072async fn test_toggle_deletion_hunk_at_start_of_file(
19073    executor: BackgroundExecutor,
19074    cx: &mut TestAppContext,
19075) {
19076    init_test(cx, |_| {});
19077    let mut cx = EditorTestContext::new(cx).await;
19078
19079    let diff_base = r#"
19080        a
19081        b
19082        c
19083        "#
19084    .unindent();
19085
19086    cx.set_state(
19087        &r#"
19088        ˇb
19089        c
19090        "#
19091        .unindent(),
19092    );
19093    cx.set_head_text(&diff_base);
19094    cx.update_editor(|editor, window, cx| {
19095        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19096    });
19097    executor.run_until_parked();
19098
19099    let hunk_expanded = r#"
19100        - a
19101          ˇb
19102          c
19103        "#
19104    .unindent();
19105
19106    cx.assert_state_with_diff(hunk_expanded.clone());
19107
19108    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19109        let snapshot = editor.snapshot(window, cx);
19110        let hunks = editor
19111            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19112            .collect::<Vec<_>>();
19113        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19114        let buffer_id = hunks[0].buffer_id;
19115        hunks
19116            .into_iter()
19117            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19118            .collect::<Vec<_>>()
19119    });
19120    assert_eq!(hunk_ranges.len(), 1);
19121
19122    cx.update_editor(|editor, _, cx| {
19123        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19124    });
19125    executor.run_until_parked();
19126
19127    let hunk_collapsed = r#"
19128          ˇb
19129          c
19130        "#
19131    .unindent();
19132
19133    cx.assert_state_with_diff(hunk_collapsed);
19134
19135    cx.update_editor(|editor, _, cx| {
19136        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19137    });
19138    executor.run_until_parked();
19139
19140    cx.assert_state_with_diff(hunk_expanded.clone());
19141}
19142
19143#[gpui::test]
19144async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19145    init_test(cx, |_| {});
19146
19147    let fs = FakeFs::new(cx.executor());
19148    fs.insert_tree(
19149        path!("/test"),
19150        json!({
19151            ".git": {},
19152            "file-1": "ONE\n",
19153            "file-2": "TWO\n",
19154            "file-3": "THREE\n",
19155        }),
19156    )
19157    .await;
19158
19159    fs.set_head_for_repo(
19160        path!("/test/.git").as_ref(),
19161        &[
19162            ("file-1".into(), "one\n".into()),
19163            ("file-2".into(), "two\n".into()),
19164            ("file-3".into(), "three\n".into()),
19165        ],
19166        "deadbeef",
19167    );
19168
19169    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19170    let mut buffers = vec![];
19171    for i in 1..=3 {
19172        let buffer = project
19173            .update(cx, |project, cx| {
19174                let path = format!(path!("/test/file-{}"), i);
19175                project.open_local_buffer(path, cx)
19176            })
19177            .await
19178            .unwrap();
19179        buffers.push(buffer);
19180    }
19181
19182    let multibuffer = cx.new(|cx| {
19183        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19184        multibuffer.set_all_diff_hunks_expanded(cx);
19185        for buffer in &buffers {
19186            let snapshot = buffer.read(cx).snapshot();
19187            multibuffer.set_excerpts_for_path(
19188                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19189                buffer.clone(),
19190                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19191                DEFAULT_MULTIBUFFER_CONTEXT,
19192                cx,
19193            );
19194        }
19195        multibuffer
19196    });
19197
19198    let editor = cx.add_window(|window, cx| {
19199        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19200    });
19201    cx.run_until_parked();
19202
19203    let snapshot = editor
19204        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19205        .unwrap();
19206    let hunks = snapshot
19207        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19208        .map(|hunk| match hunk {
19209            DisplayDiffHunk::Unfolded {
19210                display_row_range, ..
19211            } => display_row_range,
19212            DisplayDiffHunk::Folded { .. } => unreachable!(),
19213        })
19214        .collect::<Vec<_>>();
19215    assert_eq!(
19216        hunks,
19217        [
19218            DisplayRow(2)..DisplayRow(4),
19219            DisplayRow(7)..DisplayRow(9),
19220            DisplayRow(12)..DisplayRow(14),
19221        ]
19222    );
19223}
19224
19225#[gpui::test]
19226async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19227    init_test(cx, |_| {});
19228
19229    let mut cx = EditorTestContext::new(cx).await;
19230    cx.set_head_text(indoc! { "
19231        one
19232        two
19233        three
19234        four
19235        five
19236        "
19237    });
19238    cx.set_index_text(indoc! { "
19239        one
19240        two
19241        three
19242        four
19243        five
19244        "
19245    });
19246    cx.set_state(indoc! {"
19247        one
19248        TWO
19249        ˇTHREE
19250        FOUR
19251        five
19252    "});
19253    cx.run_until_parked();
19254    cx.update_editor(|editor, window, cx| {
19255        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19256    });
19257    cx.run_until_parked();
19258    cx.assert_index_text(Some(indoc! {"
19259        one
19260        TWO
19261        THREE
19262        FOUR
19263        five
19264    "}));
19265    cx.set_state(indoc! { "
19266        one
19267        TWO
19268        ˇTHREE-HUNDRED
19269        FOUR
19270        five
19271    "});
19272    cx.run_until_parked();
19273    cx.update_editor(|editor, window, cx| {
19274        let snapshot = editor.snapshot(window, cx);
19275        let hunks = editor
19276            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19277            .collect::<Vec<_>>();
19278        assert_eq!(hunks.len(), 1);
19279        assert_eq!(
19280            hunks[0].status(),
19281            DiffHunkStatus {
19282                kind: DiffHunkStatusKind::Modified,
19283                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19284            }
19285        );
19286
19287        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19288    });
19289    cx.run_until_parked();
19290    cx.assert_index_text(Some(indoc! {"
19291        one
19292        TWO
19293        THREE-HUNDRED
19294        FOUR
19295        five
19296    "}));
19297}
19298
19299#[gpui::test]
19300fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19301    init_test(cx, |_| {});
19302
19303    let editor = cx.add_window(|window, cx| {
19304        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19305        build_editor(buffer, window, cx)
19306    });
19307
19308    let render_args = Arc::new(Mutex::new(None));
19309    let snapshot = editor
19310        .update(cx, |editor, window, cx| {
19311            let snapshot = editor.buffer().read(cx).snapshot(cx);
19312            let range =
19313                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19314
19315            struct RenderArgs {
19316                row: MultiBufferRow,
19317                folded: bool,
19318                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19319            }
19320
19321            let crease = Crease::inline(
19322                range,
19323                FoldPlaceholder::test(),
19324                {
19325                    let toggle_callback = render_args.clone();
19326                    move |row, folded, callback, _window, _cx| {
19327                        *toggle_callback.lock() = Some(RenderArgs {
19328                            row,
19329                            folded,
19330                            callback,
19331                        });
19332                        div()
19333                    }
19334                },
19335                |_row, _folded, _window, _cx| div(),
19336            );
19337
19338            editor.insert_creases(Some(crease), cx);
19339            let snapshot = editor.snapshot(window, cx);
19340            let _div = snapshot.render_crease_toggle(
19341                MultiBufferRow(1),
19342                false,
19343                cx.entity().clone(),
19344                window,
19345                cx,
19346            );
19347            snapshot
19348        })
19349        .unwrap();
19350
19351    let render_args = render_args.lock().take().unwrap();
19352    assert_eq!(render_args.row, MultiBufferRow(1));
19353    assert!(!render_args.folded);
19354    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19355
19356    cx.update_window(*editor, |_, window, cx| {
19357        (render_args.callback)(true, window, cx)
19358    })
19359    .unwrap();
19360    let snapshot = editor
19361        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19362        .unwrap();
19363    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19364
19365    cx.update_window(*editor, |_, window, cx| {
19366        (render_args.callback)(false, window, cx)
19367    })
19368    .unwrap();
19369    let snapshot = editor
19370        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19371        .unwrap();
19372    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19373}
19374
19375#[gpui::test]
19376async fn test_input_text(cx: &mut TestAppContext) {
19377    init_test(cx, |_| {});
19378    let mut cx = EditorTestContext::new(cx).await;
19379
19380    cx.set_state(
19381        &r#"ˇone
19382        two
19383
19384        three
19385        fourˇ
19386        five
19387
19388        siˇx"#
19389            .unindent(),
19390    );
19391
19392    cx.dispatch_action(HandleInput(String::new()));
19393    cx.assert_editor_state(
19394        &r#"ˇone
19395        two
19396
19397        three
19398        fourˇ
19399        five
19400
19401        siˇx"#
19402            .unindent(),
19403    );
19404
19405    cx.dispatch_action(HandleInput("AAAA".to_string()));
19406    cx.assert_editor_state(
19407        &r#"AAAAˇone
19408        two
19409
19410        three
19411        fourAAAAˇ
19412        five
19413
19414        siAAAAˇx"#
19415            .unindent(),
19416    );
19417}
19418
19419#[gpui::test]
19420async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19421    init_test(cx, |_| {});
19422
19423    let mut cx = EditorTestContext::new(cx).await;
19424    cx.set_state(
19425        r#"let foo = 1;
19426let foo = 2;
19427let foo = 3;
19428let fooˇ = 4;
19429let foo = 5;
19430let foo = 6;
19431let foo = 7;
19432let foo = 8;
19433let foo = 9;
19434let foo = 10;
19435let foo = 11;
19436let foo = 12;
19437let foo = 13;
19438let foo = 14;
19439let foo = 15;"#,
19440    );
19441
19442    cx.update_editor(|e, window, cx| {
19443        assert_eq!(
19444            e.next_scroll_position,
19445            NextScrollCursorCenterTopBottom::Center,
19446            "Default next scroll direction is center",
19447        );
19448
19449        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19450        assert_eq!(
19451            e.next_scroll_position,
19452            NextScrollCursorCenterTopBottom::Top,
19453            "After center, next scroll direction should be top",
19454        );
19455
19456        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19457        assert_eq!(
19458            e.next_scroll_position,
19459            NextScrollCursorCenterTopBottom::Bottom,
19460            "After top, next scroll direction should be bottom",
19461        );
19462
19463        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19464        assert_eq!(
19465            e.next_scroll_position,
19466            NextScrollCursorCenterTopBottom::Center,
19467            "After bottom, scrolling should start over",
19468        );
19469
19470        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19471        assert_eq!(
19472            e.next_scroll_position,
19473            NextScrollCursorCenterTopBottom::Top,
19474            "Scrolling continues if retriggered fast enough"
19475        );
19476    });
19477
19478    cx.executor()
19479        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19480    cx.executor().run_until_parked();
19481    cx.update_editor(|e, _, _| {
19482        assert_eq!(
19483            e.next_scroll_position,
19484            NextScrollCursorCenterTopBottom::Center,
19485            "If scrolling is not triggered fast enough, it should reset"
19486        );
19487    });
19488}
19489
19490#[gpui::test]
19491async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19492    init_test(cx, |_| {});
19493    let mut cx = EditorLspTestContext::new_rust(
19494        lsp::ServerCapabilities {
19495            definition_provider: Some(lsp::OneOf::Left(true)),
19496            references_provider: Some(lsp::OneOf::Left(true)),
19497            ..lsp::ServerCapabilities::default()
19498        },
19499        cx,
19500    )
19501    .await;
19502
19503    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19504        let go_to_definition = cx
19505            .lsp
19506            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19507                move |params, _| async move {
19508                    if empty_go_to_definition {
19509                        Ok(None)
19510                    } else {
19511                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19512                            uri: params.text_document_position_params.text_document.uri,
19513                            range: lsp::Range::new(
19514                                lsp::Position::new(4, 3),
19515                                lsp::Position::new(4, 6),
19516                            ),
19517                        })))
19518                    }
19519                },
19520            );
19521        let references = cx
19522            .lsp
19523            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19524                Ok(Some(vec![lsp::Location {
19525                    uri: params.text_document_position.text_document.uri,
19526                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19527                }]))
19528            });
19529        (go_to_definition, references)
19530    };
19531
19532    cx.set_state(
19533        &r#"fn one() {
19534            let mut a = ˇtwo();
19535        }
19536
19537        fn two() {}"#
19538            .unindent(),
19539    );
19540    set_up_lsp_handlers(false, &mut cx);
19541    let navigated = cx
19542        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19543        .await
19544        .expect("Failed to navigate to definition");
19545    assert_eq!(
19546        navigated,
19547        Navigated::Yes,
19548        "Should have navigated to definition from the GetDefinition response"
19549    );
19550    cx.assert_editor_state(
19551        &r#"fn one() {
19552            let mut a = two();
19553        }
19554
19555        fn «twoˇ»() {}"#
19556            .unindent(),
19557    );
19558
19559    let editors = cx.update_workspace(|workspace, _, cx| {
19560        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19561    });
19562    cx.update_editor(|_, _, test_editor_cx| {
19563        assert_eq!(
19564            editors.len(),
19565            1,
19566            "Initially, only one, test, editor should be open in the workspace"
19567        );
19568        assert_eq!(
19569            test_editor_cx.entity(),
19570            editors.last().expect("Asserted len is 1").clone()
19571        );
19572    });
19573
19574    set_up_lsp_handlers(true, &mut cx);
19575    let navigated = cx
19576        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19577        .await
19578        .expect("Failed to navigate to lookup references");
19579    assert_eq!(
19580        navigated,
19581        Navigated::Yes,
19582        "Should have navigated to references as a fallback after empty GoToDefinition response"
19583    );
19584    // We should not change the selections in the existing file,
19585    // if opening another milti buffer with the references
19586    cx.assert_editor_state(
19587        &r#"fn one() {
19588            let mut a = two();
19589        }
19590
19591        fn «twoˇ»() {}"#
19592            .unindent(),
19593    );
19594    let editors = cx.update_workspace(|workspace, _, cx| {
19595        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19596    });
19597    cx.update_editor(|_, _, test_editor_cx| {
19598        assert_eq!(
19599            editors.len(),
19600            2,
19601            "After falling back to references search, we open a new editor with the results"
19602        );
19603        let references_fallback_text = editors
19604            .into_iter()
19605            .find(|new_editor| *new_editor != test_editor_cx.entity())
19606            .expect("Should have one non-test editor now")
19607            .read(test_editor_cx)
19608            .text(test_editor_cx);
19609        assert_eq!(
19610            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19611            "Should use the range from the references response and not the GoToDefinition one"
19612        );
19613    });
19614}
19615
19616#[gpui::test]
19617async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19618    init_test(cx, |_| {});
19619    cx.update(|cx| {
19620        let mut editor_settings = EditorSettings::get_global(cx).clone();
19621        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19622        EditorSettings::override_global(editor_settings, cx);
19623    });
19624    let mut cx = EditorLspTestContext::new_rust(
19625        lsp::ServerCapabilities {
19626            definition_provider: Some(lsp::OneOf::Left(true)),
19627            references_provider: Some(lsp::OneOf::Left(true)),
19628            ..lsp::ServerCapabilities::default()
19629        },
19630        cx,
19631    )
19632    .await;
19633    let original_state = r#"fn one() {
19634        let mut a = ˇtwo();
19635    }
19636
19637    fn two() {}"#
19638        .unindent();
19639    cx.set_state(&original_state);
19640
19641    let mut go_to_definition = cx
19642        .lsp
19643        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19644            move |_, _| async move { Ok(None) },
19645        );
19646    let _references = cx
19647        .lsp
19648        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19649            panic!("Should not call for references with no go to definition fallback")
19650        });
19651
19652    let navigated = cx
19653        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19654        .await
19655        .expect("Failed to navigate to lookup references");
19656    go_to_definition
19657        .next()
19658        .await
19659        .expect("Should have called the go_to_definition handler");
19660
19661    assert_eq!(
19662        navigated,
19663        Navigated::No,
19664        "Should have navigated to references as a fallback after empty GoToDefinition response"
19665    );
19666    cx.assert_editor_state(&original_state);
19667    let editors = cx.update_workspace(|workspace, _, cx| {
19668        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19669    });
19670    cx.update_editor(|_, _, _| {
19671        assert_eq!(
19672            editors.len(),
19673            1,
19674            "After unsuccessful fallback, no other editor should have been opened"
19675        );
19676    });
19677}
19678
19679#[gpui::test]
19680async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19681    init_test(cx, |_| {});
19682
19683    let language = Arc::new(Language::new(
19684        LanguageConfig::default(),
19685        Some(tree_sitter_rust::LANGUAGE.into()),
19686    ));
19687
19688    let text = r#"
19689        #[cfg(test)]
19690        mod tests() {
19691            #[test]
19692            fn runnable_1() {
19693                let a = 1;
19694            }
19695
19696            #[test]
19697            fn runnable_2() {
19698                let a = 1;
19699                let b = 2;
19700            }
19701        }
19702    "#
19703    .unindent();
19704
19705    let fs = FakeFs::new(cx.executor());
19706    fs.insert_file("/file.rs", Default::default()).await;
19707
19708    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19709    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19710    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19711    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19712    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19713
19714    let editor = cx.new_window_entity(|window, cx| {
19715        Editor::new(
19716            EditorMode::full(),
19717            multi_buffer,
19718            Some(project.clone()),
19719            window,
19720            cx,
19721        )
19722    });
19723
19724    editor.update_in(cx, |editor, window, cx| {
19725        let snapshot = editor.buffer().read(cx).snapshot(cx);
19726        editor.tasks.insert(
19727            (buffer.read(cx).remote_id(), 3),
19728            RunnableTasks {
19729                templates: vec![],
19730                offset: snapshot.anchor_before(43),
19731                column: 0,
19732                extra_variables: HashMap::default(),
19733                context_range: BufferOffset(43)..BufferOffset(85),
19734            },
19735        );
19736        editor.tasks.insert(
19737            (buffer.read(cx).remote_id(), 8),
19738            RunnableTasks {
19739                templates: vec![],
19740                offset: snapshot.anchor_before(86),
19741                column: 0,
19742                extra_variables: HashMap::default(),
19743                context_range: BufferOffset(86)..BufferOffset(191),
19744            },
19745        );
19746
19747        // Test finding task when cursor is inside function body
19748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19749            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19750        });
19751        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19752        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19753
19754        // Test finding task when cursor is on function name
19755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19756            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19757        });
19758        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19759        assert_eq!(row, 8, "Should find task when cursor is on function name");
19760    });
19761}
19762
19763#[gpui::test]
19764async fn test_folding_buffers(cx: &mut TestAppContext) {
19765    init_test(cx, |_| {});
19766
19767    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19768    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19769    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19770
19771    let fs = FakeFs::new(cx.executor());
19772    fs.insert_tree(
19773        path!("/a"),
19774        json!({
19775            "first.rs": sample_text_1,
19776            "second.rs": sample_text_2,
19777            "third.rs": sample_text_3,
19778        }),
19779    )
19780    .await;
19781    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19782    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19783    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19784    let worktree = project.update(cx, |project, cx| {
19785        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19786        assert_eq!(worktrees.len(), 1);
19787        worktrees.pop().unwrap()
19788    });
19789    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19790
19791    let buffer_1 = project
19792        .update(cx, |project, cx| {
19793            project.open_buffer((worktree_id, "first.rs"), cx)
19794        })
19795        .await
19796        .unwrap();
19797    let buffer_2 = project
19798        .update(cx, |project, cx| {
19799            project.open_buffer((worktree_id, "second.rs"), cx)
19800        })
19801        .await
19802        .unwrap();
19803    let buffer_3 = project
19804        .update(cx, |project, cx| {
19805            project.open_buffer((worktree_id, "third.rs"), cx)
19806        })
19807        .await
19808        .unwrap();
19809
19810    let multi_buffer = cx.new(|cx| {
19811        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19812        multi_buffer.push_excerpts(
19813            buffer_1.clone(),
19814            [
19815                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19816                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19817                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19818            ],
19819            cx,
19820        );
19821        multi_buffer.push_excerpts(
19822            buffer_2.clone(),
19823            [
19824                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19825                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19826                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19827            ],
19828            cx,
19829        );
19830        multi_buffer.push_excerpts(
19831            buffer_3.clone(),
19832            [
19833                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19834                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19835                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19836            ],
19837            cx,
19838        );
19839        multi_buffer
19840    });
19841    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19842        Editor::new(
19843            EditorMode::full(),
19844            multi_buffer.clone(),
19845            Some(project.clone()),
19846            window,
19847            cx,
19848        )
19849    });
19850
19851    assert_eq!(
19852        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19853        "\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",
19854    );
19855
19856    multi_buffer_editor.update(cx, |editor, cx| {
19857        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19858    });
19859    assert_eq!(
19860        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19861        "\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",
19862        "After folding the first buffer, its text should not be displayed"
19863    );
19864
19865    multi_buffer_editor.update(cx, |editor, cx| {
19866        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19867    });
19868    assert_eq!(
19869        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19870        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19871        "After folding the second buffer, its text should not be displayed"
19872    );
19873
19874    multi_buffer_editor.update(cx, |editor, cx| {
19875        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19876    });
19877    assert_eq!(
19878        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19879        "\n\n\n\n\n",
19880        "After folding the third buffer, its text should not be displayed"
19881    );
19882
19883    // Emulate selection inside the fold logic, that should work
19884    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19885        editor
19886            .snapshot(window, cx)
19887            .next_line_boundary(Point::new(0, 4));
19888    });
19889
19890    multi_buffer_editor.update(cx, |editor, cx| {
19891        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19892    });
19893    assert_eq!(
19894        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19895        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19896        "After unfolding the second buffer, its text should be displayed"
19897    );
19898
19899    // Typing inside of buffer 1 causes that buffer to be unfolded.
19900    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19901        assert_eq!(
19902            multi_buffer
19903                .read(cx)
19904                .snapshot(cx)
19905                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19906                .collect::<String>(),
19907            "bbbb"
19908        );
19909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19910            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19911        });
19912        editor.handle_input("B", window, cx);
19913    });
19914
19915    assert_eq!(
19916        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19917        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19918        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19919    );
19920
19921    multi_buffer_editor.update(cx, |editor, cx| {
19922        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19923    });
19924    assert_eq!(
19925        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19926        "\n\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",
19927        "After unfolding the all buffers, all original text should be displayed"
19928    );
19929}
19930
19931#[gpui::test]
19932async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19933    init_test(cx, |_| {});
19934
19935    let sample_text_1 = "1111\n2222\n3333".to_string();
19936    let sample_text_2 = "4444\n5555\n6666".to_string();
19937    let sample_text_3 = "7777\n8888\n9999".to_string();
19938
19939    let fs = FakeFs::new(cx.executor());
19940    fs.insert_tree(
19941        path!("/a"),
19942        json!({
19943            "first.rs": sample_text_1,
19944            "second.rs": sample_text_2,
19945            "third.rs": sample_text_3,
19946        }),
19947    )
19948    .await;
19949    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19950    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19951    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19952    let worktree = project.update(cx, |project, cx| {
19953        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19954        assert_eq!(worktrees.len(), 1);
19955        worktrees.pop().unwrap()
19956    });
19957    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19958
19959    let buffer_1 = project
19960        .update(cx, |project, cx| {
19961            project.open_buffer((worktree_id, "first.rs"), cx)
19962        })
19963        .await
19964        .unwrap();
19965    let buffer_2 = project
19966        .update(cx, |project, cx| {
19967            project.open_buffer((worktree_id, "second.rs"), cx)
19968        })
19969        .await
19970        .unwrap();
19971    let buffer_3 = project
19972        .update(cx, |project, cx| {
19973            project.open_buffer((worktree_id, "third.rs"), cx)
19974        })
19975        .await
19976        .unwrap();
19977
19978    let multi_buffer = cx.new(|cx| {
19979        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19980        multi_buffer.push_excerpts(
19981            buffer_1.clone(),
19982            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19983            cx,
19984        );
19985        multi_buffer.push_excerpts(
19986            buffer_2.clone(),
19987            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19988            cx,
19989        );
19990        multi_buffer.push_excerpts(
19991            buffer_3.clone(),
19992            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19993            cx,
19994        );
19995        multi_buffer
19996    });
19997
19998    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19999        Editor::new(
20000            EditorMode::full(),
20001            multi_buffer,
20002            Some(project.clone()),
20003            window,
20004            cx,
20005        )
20006    });
20007
20008    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20009    assert_eq!(
20010        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20011        full_text,
20012    );
20013
20014    multi_buffer_editor.update(cx, |editor, cx| {
20015        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20016    });
20017    assert_eq!(
20018        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20019        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20020        "After folding the first buffer, its text should not be displayed"
20021    );
20022
20023    multi_buffer_editor.update(cx, |editor, cx| {
20024        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20025    });
20026
20027    assert_eq!(
20028        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20029        "\n\n\n\n\n\n7777\n8888\n9999",
20030        "After folding the second buffer, its text should not be displayed"
20031    );
20032
20033    multi_buffer_editor.update(cx, |editor, cx| {
20034        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20035    });
20036    assert_eq!(
20037        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20038        "\n\n\n\n\n",
20039        "After folding the third buffer, its text should not be displayed"
20040    );
20041
20042    multi_buffer_editor.update(cx, |editor, cx| {
20043        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20044    });
20045    assert_eq!(
20046        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20047        "\n\n\n\n4444\n5555\n6666\n\n",
20048        "After unfolding the second buffer, its text should be displayed"
20049    );
20050
20051    multi_buffer_editor.update(cx, |editor, cx| {
20052        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20053    });
20054    assert_eq!(
20055        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20056        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20057        "After unfolding the first buffer, its text should be displayed"
20058    );
20059
20060    multi_buffer_editor.update(cx, |editor, cx| {
20061        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20062    });
20063    assert_eq!(
20064        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20065        full_text,
20066        "After unfolding all buffers, all original text should be displayed"
20067    );
20068}
20069
20070#[gpui::test]
20071async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20072    init_test(cx, |_| {});
20073
20074    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20075
20076    let fs = FakeFs::new(cx.executor());
20077    fs.insert_tree(
20078        path!("/a"),
20079        json!({
20080            "main.rs": sample_text,
20081        }),
20082    )
20083    .await;
20084    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20085    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20086    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20087    let worktree = project.update(cx, |project, cx| {
20088        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20089        assert_eq!(worktrees.len(), 1);
20090        worktrees.pop().unwrap()
20091    });
20092    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20093
20094    let buffer_1 = project
20095        .update(cx, |project, cx| {
20096            project.open_buffer((worktree_id, "main.rs"), cx)
20097        })
20098        .await
20099        .unwrap();
20100
20101    let multi_buffer = cx.new(|cx| {
20102        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20103        multi_buffer.push_excerpts(
20104            buffer_1.clone(),
20105            [ExcerptRange::new(
20106                Point::new(0, 0)
20107                    ..Point::new(
20108                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20109                        0,
20110                    ),
20111            )],
20112            cx,
20113        );
20114        multi_buffer
20115    });
20116    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20117        Editor::new(
20118            EditorMode::full(),
20119            multi_buffer,
20120            Some(project.clone()),
20121            window,
20122            cx,
20123        )
20124    });
20125
20126    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20127    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20128        enum TestHighlight {}
20129        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20130        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20131        editor.highlight_text::<TestHighlight>(
20132            vec![highlight_range.clone()],
20133            HighlightStyle::color(Hsla::green()),
20134            cx,
20135        );
20136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20137            s.select_ranges(Some(highlight_range))
20138        });
20139    });
20140
20141    let full_text = format!("\n\n{sample_text}");
20142    assert_eq!(
20143        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20144        full_text,
20145    );
20146}
20147
20148#[gpui::test]
20149async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20150    init_test(cx, |_| {});
20151    cx.update(|cx| {
20152        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20153            "keymaps/default-linux.json",
20154            cx,
20155        )
20156        .unwrap();
20157        cx.bind_keys(default_key_bindings);
20158    });
20159
20160    let (editor, cx) = cx.add_window_view(|window, cx| {
20161        let multi_buffer = MultiBuffer::build_multi(
20162            [
20163                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20164                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20165                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20166                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20167            ],
20168            cx,
20169        );
20170        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20171
20172        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20173        // fold all but the second buffer, so that we test navigating between two
20174        // adjacent folded buffers, as well as folded buffers at the start and
20175        // end the multibuffer
20176        editor.fold_buffer(buffer_ids[0], cx);
20177        editor.fold_buffer(buffer_ids[2], cx);
20178        editor.fold_buffer(buffer_ids[3], cx);
20179
20180        editor
20181    });
20182    cx.simulate_resize(size(px(1000.), px(1000.)));
20183
20184    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20185    cx.assert_excerpts_with_selections(indoc! {"
20186        [EXCERPT]
20187        ˇ[FOLDED]
20188        [EXCERPT]
20189        a1
20190        b1
20191        [EXCERPT]
20192        [FOLDED]
20193        [EXCERPT]
20194        [FOLDED]
20195        "
20196    });
20197    cx.simulate_keystroke("down");
20198    cx.assert_excerpts_with_selections(indoc! {"
20199        [EXCERPT]
20200        [FOLDED]
20201        [EXCERPT]
20202        ˇa1
20203        b1
20204        [EXCERPT]
20205        [FOLDED]
20206        [EXCERPT]
20207        [FOLDED]
20208        "
20209    });
20210    cx.simulate_keystroke("down");
20211    cx.assert_excerpts_with_selections(indoc! {"
20212        [EXCERPT]
20213        [FOLDED]
20214        [EXCERPT]
20215        a1
20216        ˇb1
20217        [EXCERPT]
20218        [FOLDED]
20219        [EXCERPT]
20220        [FOLDED]
20221        "
20222    });
20223    cx.simulate_keystroke("down");
20224    cx.assert_excerpts_with_selections(indoc! {"
20225        [EXCERPT]
20226        [FOLDED]
20227        [EXCERPT]
20228        a1
20229        b1
20230        ˇ[EXCERPT]
20231        [FOLDED]
20232        [EXCERPT]
20233        [FOLDED]
20234        "
20235    });
20236    cx.simulate_keystroke("down");
20237    cx.assert_excerpts_with_selections(indoc! {"
20238        [EXCERPT]
20239        [FOLDED]
20240        [EXCERPT]
20241        a1
20242        b1
20243        [EXCERPT]
20244        ˇ[FOLDED]
20245        [EXCERPT]
20246        [FOLDED]
20247        "
20248    });
20249    for _ in 0..5 {
20250        cx.simulate_keystroke("down");
20251        cx.assert_excerpts_with_selections(indoc! {"
20252            [EXCERPT]
20253            [FOLDED]
20254            [EXCERPT]
20255            a1
20256            b1
20257            [EXCERPT]
20258            [FOLDED]
20259            [EXCERPT]
20260            ˇ[FOLDED]
20261            "
20262        });
20263    }
20264
20265    cx.simulate_keystroke("up");
20266    cx.assert_excerpts_with_selections(indoc! {"
20267        [EXCERPT]
20268        [FOLDED]
20269        [EXCERPT]
20270        a1
20271        b1
20272        [EXCERPT]
20273        ˇ[FOLDED]
20274        [EXCERPT]
20275        [FOLDED]
20276        "
20277    });
20278    cx.simulate_keystroke("up");
20279    cx.assert_excerpts_with_selections(indoc! {"
20280        [EXCERPT]
20281        [FOLDED]
20282        [EXCERPT]
20283        a1
20284        b1
20285        ˇ[EXCERPT]
20286        [FOLDED]
20287        [EXCERPT]
20288        [FOLDED]
20289        "
20290    });
20291    cx.simulate_keystroke("up");
20292    cx.assert_excerpts_with_selections(indoc! {"
20293        [EXCERPT]
20294        [FOLDED]
20295        [EXCERPT]
20296        a1
20297        ˇb1
20298        [EXCERPT]
20299        [FOLDED]
20300        [EXCERPT]
20301        [FOLDED]
20302        "
20303    });
20304    cx.simulate_keystroke("up");
20305    cx.assert_excerpts_with_selections(indoc! {"
20306        [EXCERPT]
20307        [FOLDED]
20308        [EXCERPT]
20309        ˇa1
20310        b1
20311        [EXCERPT]
20312        [FOLDED]
20313        [EXCERPT]
20314        [FOLDED]
20315        "
20316    });
20317    for _ in 0..5 {
20318        cx.simulate_keystroke("up");
20319        cx.assert_excerpts_with_selections(indoc! {"
20320            [EXCERPT]
20321            ˇ[FOLDED]
20322            [EXCERPT]
20323            a1
20324            b1
20325            [EXCERPT]
20326            [FOLDED]
20327            [EXCERPT]
20328            [FOLDED]
20329            "
20330        });
20331    }
20332}
20333
20334#[gpui::test]
20335async fn test_inline_completion_text(cx: &mut TestAppContext) {
20336    init_test(cx, |_| {});
20337
20338    // Simple insertion
20339    assert_highlighted_edits(
20340        "Hello, world!",
20341        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20342        true,
20343        cx,
20344        |highlighted_edits, cx| {
20345            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20346            assert_eq!(highlighted_edits.highlights.len(), 1);
20347            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20348            assert_eq!(
20349                highlighted_edits.highlights[0].1.background_color,
20350                Some(cx.theme().status().created_background)
20351            );
20352        },
20353    )
20354    .await;
20355
20356    // Replacement
20357    assert_highlighted_edits(
20358        "This is a test.",
20359        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20360        false,
20361        cx,
20362        |highlighted_edits, cx| {
20363            assert_eq!(highlighted_edits.text, "That is a test.");
20364            assert_eq!(highlighted_edits.highlights.len(), 1);
20365            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20366            assert_eq!(
20367                highlighted_edits.highlights[0].1.background_color,
20368                Some(cx.theme().status().created_background)
20369            );
20370        },
20371    )
20372    .await;
20373
20374    // Multiple edits
20375    assert_highlighted_edits(
20376        "Hello, world!",
20377        vec![
20378            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20379            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20380        ],
20381        false,
20382        cx,
20383        |highlighted_edits, cx| {
20384            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20385            assert_eq!(highlighted_edits.highlights.len(), 2);
20386            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20387            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20388            assert_eq!(
20389                highlighted_edits.highlights[0].1.background_color,
20390                Some(cx.theme().status().created_background)
20391            );
20392            assert_eq!(
20393                highlighted_edits.highlights[1].1.background_color,
20394                Some(cx.theme().status().created_background)
20395            );
20396        },
20397    )
20398    .await;
20399
20400    // Multiple lines with edits
20401    assert_highlighted_edits(
20402        "First line\nSecond line\nThird line\nFourth line",
20403        vec![
20404            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20405            (
20406                Point::new(2, 0)..Point::new(2, 10),
20407                "New third line".to_string(),
20408            ),
20409            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20410        ],
20411        false,
20412        cx,
20413        |highlighted_edits, cx| {
20414            assert_eq!(
20415                highlighted_edits.text,
20416                "Second modified\nNew third line\nFourth updated line"
20417            );
20418            assert_eq!(highlighted_edits.highlights.len(), 3);
20419            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20420            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20421            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20422            for highlight in &highlighted_edits.highlights {
20423                assert_eq!(
20424                    highlight.1.background_color,
20425                    Some(cx.theme().status().created_background)
20426                );
20427            }
20428        },
20429    )
20430    .await;
20431}
20432
20433#[gpui::test]
20434async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20435    init_test(cx, |_| {});
20436
20437    // Deletion
20438    assert_highlighted_edits(
20439        "Hello, world!",
20440        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20441        true,
20442        cx,
20443        |highlighted_edits, cx| {
20444            assert_eq!(highlighted_edits.text, "Hello, world!");
20445            assert_eq!(highlighted_edits.highlights.len(), 1);
20446            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20447            assert_eq!(
20448                highlighted_edits.highlights[0].1.background_color,
20449                Some(cx.theme().status().deleted_background)
20450            );
20451        },
20452    )
20453    .await;
20454
20455    // Insertion
20456    assert_highlighted_edits(
20457        "Hello, world!",
20458        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20459        true,
20460        cx,
20461        |highlighted_edits, cx| {
20462            assert_eq!(highlighted_edits.highlights.len(), 1);
20463            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20464            assert_eq!(
20465                highlighted_edits.highlights[0].1.background_color,
20466                Some(cx.theme().status().created_background)
20467            );
20468        },
20469    )
20470    .await;
20471}
20472
20473async fn assert_highlighted_edits(
20474    text: &str,
20475    edits: Vec<(Range<Point>, String)>,
20476    include_deletions: bool,
20477    cx: &mut TestAppContext,
20478    assertion_fn: impl Fn(HighlightedText, &App),
20479) {
20480    let window = cx.add_window(|window, cx| {
20481        let buffer = MultiBuffer::build_simple(text, cx);
20482        Editor::new(EditorMode::full(), buffer, None, window, cx)
20483    });
20484    let cx = &mut VisualTestContext::from_window(*window, cx);
20485
20486    let (buffer, snapshot) = window
20487        .update(cx, |editor, _window, cx| {
20488            (
20489                editor.buffer().clone(),
20490                editor.buffer().read(cx).snapshot(cx),
20491            )
20492        })
20493        .unwrap();
20494
20495    let edits = edits
20496        .into_iter()
20497        .map(|(range, edit)| {
20498            (
20499                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20500                edit,
20501            )
20502        })
20503        .collect::<Vec<_>>();
20504
20505    let text_anchor_edits = edits
20506        .clone()
20507        .into_iter()
20508        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20509        .collect::<Vec<_>>();
20510
20511    let edit_preview = window
20512        .update(cx, |_, _window, cx| {
20513            buffer
20514                .read(cx)
20515                .as_singleton()
20516                .unwrap()
20517                .read(cx)
20518                .preview_edits(text_anchor_edits.into(), cx)
20519        })
20520        .unwrap()
20521        .await;
20522
20523    cx.update(|_window, cx| {
20524        let highlighted_edits = inline_completion_edit_text(
20525            &snapshot.as_singleton().unwrap().2,
20526            &edits,
20527            &edit_preview,
20528            include_deletions,
20529            cx,
20530        );
20531        assertion_fn(highlighted_edits, cx)
20532    });
20533}
20534
20535#[track_caller]
20536fn assert_breakpoint(
20537    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20538    path: &Arc<Path>,
20539    expected: Vec<(u32, Breakpoint)>,
20540) {
20541    if expected.len() == 0usize {
20542        assert!(!breakpoints.contains_key(path), "{}", path.display());
20543    } else {
20544        let mut breakpoint = breakpoints
20545            .get(path)
20546            .unwrap()
20547            .into_iter()
20548            .map(|breakpoint| {
20549                (
20550                    breakpoint.row,
20551                    Breakpoint {
20552                        message: breakpoint.message.clone(),
20553                        state: breakpoint.state,
20554                        condition: breakpoint.condition.clone(),
20555                        hit_condition: breakpoint.hit_condition.clone(),
20556                    },
20557                )
20558            })
20559            .collect::<Vec<_>>();
20560
20561        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20562
20563        assert_eq!(expected, breakpoint);
20564    }
20565}
20566
20567fn add_log_breakpoint_at_cursor(
20568    editor: &mut Editor,
20569    log_message: &str,
20570    window: &mut Window,
20571    cx: &mut Context<Editor>,
20572) {
20573    let (anchor, bp) = editor
20574        .breakpoints_at_cursors(window, cx)
20575        .first()
20576        .and_then(|(anchor, bp)| {
20577            if let Some(bp) = bp {
20578                Some((*anchor, bp.clone()))
20579            } else {
20580                None
20581            }
20582        })
20583        .unwrap_or_else(|| {
20584            let cursor_position: Point = editor.selections.newest(cx).head();
20585
20586            let breakpoint_position = editor
20587                .snapshot(window, cx)
20588                .display_snapshot
20589                .buffer_snapshot
20590                .anchor_before(Point::new(cursor_position.row, 0));
20591
20592            (breakpoint_position, Breakpoint::new_log(&log_message))
20593        });
20594
20595    editor.edit_breakpoint_at_anchor(
20596        anchor,
20597        bp,
20598        BreakpointEditAction::EditLogMessage(log_message.into()),
20599        cx,
20600    );
20601}
20602
20603#[gpui::test]
20604async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20605    init_test(cx, |_| {});
20606
20607    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20608    let fs = FakeFs::new(cx.executor());
20609    fs.insert_tree(
20610        path!("/a"),
20611        json!({
20612            "main.rs": sample_text,
20613        }),
20614    )
20615    .await;
20616    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20617    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20618    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20619
20620    let fs = FakeFs::new(cx.executor());
20621    fs.insert_tree(
20622        path!("/a"),
20623        json!({
20624            "main.rs": sample_text,
20625        }),
20626    )
20627    .await;
20628    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20629    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20630    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20631    let worktree_id = workspace
20632        .update(cx, |workspace, _window, cx| {
20633            workspace.project().update(cx, |project, cx| {
20634                project.worktrees(cx).next().unwrap().read(cx).id()
20635            })
20636        })
20637        .unwrap();
20638
20639    let buffer = project
20640        .update(cx, |project, cx| {
20641            project.open_buffer((worktree_id, "main.rs"), cx)
20642        })
20643        .await
20644        .unwrap();
20645
20646    let (editor, cx) = cx.add_window_view(|window, cx| {
20647        Editor::new(
20648            EditorMode::full(),
20649            MultiBuffer::build_from_buffer(buffer, cx),
20650            Some(project.clone()),
20651            window,
20652            cx,
20653        )
20654    });
20655
20656    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20657    let abs_path = project.read_with(cx, |project, cx| {
20658        project
20659            .absolute_path(&project_path, cx)
20660            .map(|path_buf| Arc::from(path_buf.to_owned()))
20661            .unwrap()
20662    });
20663
20664    // assert we can add breakpoint on the first line
20665    editor.update_in(cx, |editor, window, cx| {
20666        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20667        editor.move_to_end(&MoveToEnd, window, cx);
20668        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20669    });
20670
20671    let breakpoints = editor.update(cx, |editor, cx| {
20672        editor
20673            .breakpoint_store()
20674            .as_ref()
20675            .unwrap()
20676            .read(cx)
20677            .all_source_breakpoints(cx)
20678            .clone()
20679    });
20680
20681    assert_eq!(1, breakpoints.len());
20682    assert_breakpoint(
20683        &breakpoints,
20684        &abs_path,
20685        vec![
20686            (0, Breakpoint::new_standard()),
20687            (3, Breakpoint::new_standard()),
20688        ],
20689    );
20690
20691    editor.update_in(cx, |editor, window, cx| {
20692        editor.move_to_beginning(&MoveToBeginning, window, cx);
20693        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20694    });
20695
20696    let breakpoints = editor.update(cx, |editor, cx| {
20697        editor
20698            .breakpoint_store()
20699            .as_ref()
20700            .unwrap()
20701            .read(cx)
20702            .all_source_breakpoints(cx)
20703            .clone()
20704    });
20705
20706    assert_eq!(1, breakpoints.len());
20707    assert_breakpoint(
20708        &breakpoints,
20709        &abs_path,
20710        vec![(3, Breakpoint::new_standard())],
20711    );
20712
20713    editor.update_in(cx, |editor, window, cx| {
20714        editor.move_to_end(&MoveToEnd, window, cx);
20715        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20716    });
20717
20718    let breakpoints = editor.update(cx, |editor, cx| {
20719        editor
20720            .breakpoint_store()
20721            .as_ref()
20722            .unwrap()
20723            .read(cx)
20724            .all_source_breakpoints(cx)
20725            .clone()
20726    });
20727
20728    assert_eq!(0, breakpoints.len());
20729    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20730}
20731
20732#[gpui::test]
20733async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20734    init_test(cx, |_| {});
20735
20736    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20737
20738    let fs = FakeFs::new(cx.executor());
20739    fs.insert_tree(
20740        path!("/a"),
20741        json!({
20742            "main.rs": sample_text,
20743        }),
20744    )
20745    .await;
20746    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20747    let (workspace, cx) =
20748        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20749
20750    let worktree_id = workspace.update(cx, |workspace, cx| {
20751        workspace.project().update(cx, |project, cx| {
20752            project.worktrees(cx).next().unwrap().read(cx).id()
20753        })
20754    });
20755
20756    let buffer = project
20757        .update(cx, |project, cx| {
20758            project.open_buffer((worktree_id, "main.rs"), cx)
20759        })
20760        .await
20761        .unwrap();
20762
20763    let (editor, cx) = cx.add_window_view(|window, cx| {
20764        Editor::new(
20765            EditorMode::full(),
20766            MultiBuffer::build_from_buffer(buffer, cx),
20767            Some(project.clone()),
20768            window,
20769            cx,
20770        )
20771    });
20772
20773    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20774    let abs_path = project.read_with(cx, |project, cx| {
20775        project
20776            .absolute_path(&project_path, cx)
20777            .map(|path_buf| Arc::from(path_buf.to_owned()))
20778            .unwrap()
20779    });
20780
20781    editor.update_in(cx, |editor, window, cx| {
20782        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20783    });
20784
20785    let breakpoints = editor.update(cx, |editor, cx| {
20786        editor
20787            .breakpoint_store()
20788            .as_ref()
20789            .unwrap()
20790            .read(cx)
20791            .all_source_breakpoints(cx)
20792            .clone()
20793    });
20794
20795    assert_breakpoint(
20796        &breakpoints,
20797        &abs_path,
20798        vec![(0, Breakpoint::new_log("hello world"))],
20799    );
20800
20801    // Removing a log message from a log breakpoint should remove it
20802    editor.update_in(cx, |editor, window, cx| {
20803        add_log_breakpoint_at_cursor(editor, "", window, cx);
20804    });
20805
20806    let breakpoints = editor.update(cx, |editor, cx| {
20807        editor
20808            .breakpoint_store()
20809            .as_ref()
20810            .unwrap()
20811            .read(cx)
20812            .all_source_breakpoints(cx)
20813            .clone()
20814    });
20815
20816    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20817
20818    editor.update_in(cx, |editor, window, cx| {
20819        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20820        editor.move_to_end(&MoveToEnd, window, cx);
20821        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20822        // Not adding a log message to a standard breakpoint shouldn't remove it
20823        add_log_breakpoint_at_cursor(editor, "", window, cx);
20824    });
20825
20826    let breakpoints = editor.update(cx, |editor, cx| {
20827        editor
20828            .breakpoint_store()
20829            .as_ref()
20830            .unwrap()
20831            .read(cx)
20832            .all_source_breakpoints(cx)
20833            .clone()
20834    });
20835
20836    assert_breakpoint(
20837        &breakpoints,
20838        &abs_path,
20839        vec![
20840            (0, Breakpoint::new_standard()),
20841            (3, Breakpoint::new_standard()),
20842        ],
20843    );
20844
20845    editor.update_in(cx, |editor, window, cx| {
20846        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20847    });
20848
20849    let breakpoints = editor.update(cx, |editor, cx| {
20850        editor
20851            .breakpoint_store()
20852            .as_ref()
20853            .unwrap()
20854            .read(cx)
20855            .all_source_breakpoints(cx)
20856            .clone()
20857    });
20858
20859    assert_breakpoint(
20860        &breakpoints,
20861        &abs_path,
20862        vec![
20863            (0, Breakpoint::new_standard()),
20864            (3, Breakpoint::new_log("hello world")),
20865        ],
20866    );
20867
20868    editor.update_in(cx, |editor, window, cx| {
20869        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20870    });
20871
20872    let breakpoints = editor.update(cx, |editor, cx| {
20873        editor
20874            .breakpoint_store()
20875            .as_ref()
20876            .unwrap()
20877            .read(cx)
20878            .all_source_breakpoints(cx)
20879            .clone()
20880    });
20881
20882    assert_breakpoint(
20883        &breakpoints,
20884        &abs_path,
20885        vec![
20886            (0, Breakpoint::new_standard()),
20887            (3, Breakpoint::new_log("hello Earth!!")),
20888        ],
20889    );
20890}
20891
20892/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20893/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20894/// or when breakpoints were placed out of order. This tests for a regression too
20895#[gpui::test]
20896async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20897    init_test(cx, |_| {});
20898
20899    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20900    let fs = FakeFs::new(cx.executor());
20901    fs.insert_tree(
20902        path!("/a"),
20903        json!({
20904            "main.rs": sample_text,
20905        }),
20906    )
20907    .await;
20908    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20909    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20910    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20911
20912    let fs = FakeFs::new(cx.executor());
20913    fs.insert_tree(
20914        path!("/a"),
20915        json!({
20916            "main.rs": sample_text,
20917        }),
20918    )
20919    .await;
20920    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20921    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20922    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20923    let worktree_id = workspace
20924        .update(cx, |workspace, _window, cx| {
20925            workspace.project().update(cx, |project, cx| {
20926                project.worktrees(cx).next().unwrap().read(cx).id()
20927            })
20928        })
20929        .unwrap();
20930
20931    let buffer = project
20932        .update(cx, |project, cx| {
20933            project.open_buffer((worktree_id, "main.rs"), cx)
20934        })
20935        .await
20936        .unwrap();
20937
20938    let (editor, cx) = cx.add_window_view(|window, cx| {
20939        Editor::new(
20940            EditorMode::full(),
20941            MultiBuffer::build_from_buffer(buffer, cx),
20942            Some(project.clone()),
20943            window,
20944            cx,
20945        )
20946    });
20947
20948    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20949    let abs_path = project.read_with(cx, |project, cx| {
20950        project
20951            .absolute_path(&project_path, cx)
20952            .map(|path_buf| Arc::from(path_buf.to_owned()))
20953            .unwrap()
20954    });
20955
20956    // assert we can add breakpoint on the first line
20957    editor.update_in(cx, |editor, window, cx| {
20958        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20959        editor.move_to_end(&MoveToEnd, window, cx);
20960        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20961        editor.move_up(&MoveUp, window, cx);
20962        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20963    });
20964
20965    let breakpoints = editor.update(cx, |editor, cx| {
20966        editor
20967            .breakpoint_store()
20968            .as_ref()
20969            .unwrap()
20970            .read(cx)
20971            .all_source_breakpoints(cx)
20972            .clone()
20973    });
20974
20975    assert_eq!(1, breakpoints.len());
20976    assert_breakpoint(
20977        &breakpoints,
20978        &abs_path,
20979        vec![
20980            (0, Breakpoint::new_standard()),
20981            (2, Breakpoint::new_standard()),
20982            (3, Breakpoint::new_standard()),
20983        ],
20984    );
20985
20986    editor.update_in(cx, |editor, window, cx| {
20987        editor.move_to_beginning(&MoveToBeginning, window, cx);
20988        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20989        editor.move_to_end(&MoveToEnd, window, cx);
20990        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20991        // Disabling a breakpoint that doesn't exist should do nothing
20992        editor.move_up(&MoveUp, window, cx);
20993        editor.move_up(&MoveUp, window, cx);
20994        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20995    });
20996
20997    let breakpoints = editor.update(cx, |editor, cx| {
20998        editor
20999            .breakpoint_store()
21000            .as_ref()
21001            .unwrap()
21002            .read(cx)
21003            .all_source_breakpoints(cx)
21004            .clone()
21005    });
21006
21007    let disable_breakpoint = {
21008        let mut bp = Breakpoint::new_standard();
21009        bp.state = BreakpointState::Disabled;
21010        bp
21011    };
21012
21013    assert_eq!(1, breakpoints.len());
21014    assert_breakpoint(
21015        &breakpoints,
21016        &abs_path,
21017        vec![
21018            (0, disable_breakpoint.clone()),
21019            (2, Breakpoint::new_standard()),
21020            (3, disable_breakpoint.clone()),
21021        ],
21022    );
21023
21024    editor.update_in(cx, |editor, window, cx| {
21025        editor.move_to_beginning(&MoveToBeginning, window, cx);
21026        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21027        editor.move_to_end(&MoveToEnd, window, cx);
21028        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21029        editor.move_up(&MoveUp, window, cx);
21030        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21031    });
21032
21033    let breakpoints = editor.update(cx, |editor, cx| {
21034        editor
21035            .breakpoint_store()
21036            .as_ref()
21037            .unwrap()
21038            .read(cx)
21039            .all_source_breakpoints(cx)
21040            .clone()
21041    });
21042
21043    assert_eq!(1, breakpoints.len());
21044    assert_breakpoint(
21045        &breakpoints,
21046        &abs_path,
21047        vec![
21048            (0, Breakpoint::new_standard()),
21049            (2, disable_breakpoint),
21050            (3, Breakpoint::new_standard()),
21051        ],
21052    );
21053}
21054
21055#[gpui::test]
21056async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21057    init_test(cx, |_| {});
21058    let capabilities = lsp::ServerCapabilities {
21059        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21060            prepare_provider: Some(true),
21061            work_done_progress_options: Default::default(),
21062        })),
21063        ..Default::default()
21064    };
21065    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21066
21067    cx.set_state(indoc! {"
21068        struct Fˇoo {}
21069    "});
21070
21071    cx.update_editor(|editor, _, cx| {
21072        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21073        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21074        editor.highlight_background::<DocumentHighlightRead>(
21075            &[highlight_range],
21076            |theme| theme.colors().editor_document_highlight_read_background,
21077            cx,
21078        );
21079    });
21080
21081    let mut prepare_rename_handler = cx
21082        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21083            move |_, _, _| async move {
21084                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21085                    start: lsp::Position {
21086                        line: 0,
21087                        character: 7,
21088                    },
21089                    end: lsp::Position {
21090                        line: 0,
21091                        character: 10,
21092                    },
21093                })))
21094            },
21095        );
21096    let prepare_rename_task = cx
21097        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21098        .expect("Prepare rename was not started");
21099    prepare_rename_handler.next().await.unwrap();
21100    prepare_rename_task.await.expect("Prepare rename failed");
21101
21102    let mut rename_handler =
21103        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21104            let edit = lsp::TextEdit {
21105                range: lsp::Range {
21106                    start: lsp::Position {
21107                        line: 0,
21108                        character: 7,
21109                    },
21110                    end: lsp::Position {
21111                        line: 0,
21112                        character: 10,
21113                    },
21114                },
21115                new_text: "FooRenamed".to_string(),
21116            };
21117            Ok(Some(lsp::WorkspaceEdit::new(
21118                // Specify the same edit twice
21119                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21120            )))
21121        });
21122    let rename_task = cx
21123        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21124        .expect("Confirm rename was not started");
21125    rename_handler.next().await.unwrap();
21126    rename_task.await.expect("Confirm rename failed");
21127    cx.run_until_parked();
21128
21129    // Despite two edits, only one is actually applied as those are identical
21130    cx.assert_editor_state(indoc! {"
21131        struct FooRenamedˇ {}
21132    "});
21133}
21134
21135#[gpui::test]
21136async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21137    init_test(cx, |_| {});
21138    // These capabilities indicate that the server does not support prepare rename.
21139    let capabilities = lsp::ServerCapabilities {
21140        rename_provider: Some(lsp::OneOf::Left(true)),
21141        ..Default::default()
21142    };
21143    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21144
21145    cx.set_state(indoc! {"
21146        struct Fˇoo {}
21147    "});
21148
21149    cx.update_editor(|editor, _window, cx| {
21150        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21151        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21152        editor.highlight_background::<DocumentHighlightRead>(
21153            &[highlight_range],
21154            |theme| theme.colors().editor_document_highlight_read_background,
21155            cx,
21156        );
21157    });
21158
21159    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21160        .expect("Prepare rename was not started")
21161        .await
21162        .expect("Prepare rename failed");
21163
21164    let mut rename_handler =
21165        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21166            let edit = lsp::TextEdit {
21167                range: lsp::Range {
21168                    start: lsp::Position {
21169                        line: 0,
21170                        character: 7,
21171                    },
21172                    end: lsp::Position {
21173                        line: 0,
21174                        character: 10,
21175                    },
21176                },
21177                new_text: "FooRenamed".to_string(),
21178            };
21179            Ok(Some(lsp::WorkspaceEdit::new(
21180                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21181            )))
21182        });
21183    let rename_task = cx
21184        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21185        .expect("Confirm rename was not started");
21186    rename_handler.next().await.unwrap();
21187    rename_task.await.expect("Confirm rename failed");
21188    cx.run_until_parked();
21189
21190    // Correct range is renamed, as `surrounding_word` is used to find it.
21191    cx.assert_editor_state(indoc! {"
21192        struct FooRenamedˇ {}
21193    "});
21194}
21195
21196#[gpui::test]
21197async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21198    init_test(cx, |_| {});
21199    let mut cx = EditorTestContext::new(cx).await;
21200
21201    let language = Arc::new(
21202        Language::new(
21203            LanguageConfig::default(),
21204            Some(tree_sitter_html::LANGUAGE.into()),
21205        )
21206        .with_brackets_query(
21207            r#"
21208            ("<" @open "/>" @close)
21209            ("</" @open ">" @close)
21210            ("<" @open ">" @close)
21211            ("\"" @open "\"" @close)
21212            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21213        "#,
21214        )
21215        .unwrap(),
21216    );
21217    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21218
21219    cx.set_state(indoc! {"
21220        <span>ˇ</span>
21221    "});
21222    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21223    cx.assert_editor_state(indoc! {"
21224        <span>
21225        ˇ
21226        </span>
21227    "});
21228
21229    cx.set_state(indoc! {"
21230        <span><span></span>ˇ</span>
21231    "});
21232    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21233    cx.assert_editor_state(indoc! {"
21234        <span><span></span>
21235        ˇ</span>
21236    "});
21237
21238    cx.set_state(indoc! {"
21239        <span>ˇ
21240        </span>
21241    "});
21242    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21243    cx.assert_editor_state(indoc! {"
21244        <span>
21245        ˇ
21246        </span>
21247    "});
21248}
21249
21250#[gpui::test(iterations = 10)]
21251async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21252    init_test(cx, |_| {});
21253
21254    let fs = FakeFs::new(cx.executor());
21255    fs.insert_tree(
21256        path!("/dir"),
21257        json!({
21258            "a.ts": "a",
21259        }),
21260    )
21261    .await;
21262
21263    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21264    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21265    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21266
21267    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21268    language_registry.add(Arc::new(Language::new(
21269        LanguageConfig {
21270            name: "TypeScript".into(),
21271            matcher: LanguageMatcher {
21272                path_suffixes: vec!["ts".to_string()],
21273                ..Default::default()
21274            },
21275            ..Default::default()
21276        },
21277        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21278    )));
21279    let mut fake_language_servers = language_registry.register_fake_lsp(
21280        "TypeScript",
21281        FakeLspAdapter {
21282            capabilities: lsp::ServerCapabilities {
21283                code_lens_provider: Some(lsp::CodeLensOptions {
21284                    resolve_provider: Some(true),
21285                }),
21286                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21287                    commands: vec!["_the/command".to_string()],
21288                    ..lsp::ExecuteCommandOptions::default()
21289                }),
21290                ..lsp::ServerCapabilities::default()
21291            },
21292            ..FakeLspAdapter::default()
21293        },
21294    );
21295
21296    let (buffer, _handle) = project
21297        .update(cx, |p, cx| {
21298            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21299        })
21300        .await
21301        .unwrap();
21302    cx.executor().run_until_parked();
21303
21304    let fake_server = fake_language_servers.next().await.unwrap();
21305
21306    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21307    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21308    drop(buffer_snapshot);
21309    let actions = cx
21310        .update_window(*workspace, |_, window, cx| {
21311            project.code_actions(&buffer, anchor..anchor, window, cx)
21312        })
21313        .unwrap();
21314
21315    fake_server
21316        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21317            Ok(Some(vec![
21318                lsp::CodeLens {
21319                    range: lsp::Range::default(),
21320                    command: Some(lsp::Command {
21321                        title: "Code lens command".to_owned(),
21322                        command: "_the/command".to_owned(),
21323                        arguments: None,
21324                    }),
21325                    data: None,
21326                },
21327                lsp::CodeLens {
21328                    range: lsp::Range::default(),
21329                    command: Some(lsp::Command {
21330                        title: "Command not in capabilities".to_owned(),
21331                        command: "not in capabilities".to_owned(),
21332                        arguments: None,
21333                    }),
21334                    data: None,
21335                },
21336                lsp::CodeLens {
21337                    range: lsp::Range {
21338                        start: lsp::Position {
21339                            line: 1,
21340                            character: 1,
21341                        },
21342                        end: lsp::Position {
21343                            line: 1,
21344                            character: 1,
21345                        },
21346                    },
21347                    command: Some(lsp::Command {
21348                        title: "Command not in range".to_owned(),
21349                        command: "_the/command".to_owned(),
21350                        arguments: None,
21351                    }),
21352                    data: None,
21353                },
21354            ]))
21355        })
21356        .next()
21357        .await;
21358
21359    let actions = actions.await.unwrap();
21360    assert_eq!(
21361        actions.len(),
21362        1,
21363        "Should have only one valid action for the 0..0 range"
21364    );
21365    let action = actions[0].clone();
21366    let apply = project.update(cx, |project, cx| {
21367        project.apply_code_action(buffer.clone(), action, true, cx)
21368    });
21369
21370    // Resolving the code action does not populate its edits. In absence of
21371    // edits, we must execute the given command.
21372    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21373        |mut lens, _| async move {
21374            let lens_command = lens.command.as_mut().expect("should have a command");
21375            assert_eq!(lens_command.title, "Code lens command");
21376            lens_command.arguments = Some(vec![json!("the-argument")]);
21377            Ok(lens)
21378        },
21379    );
21380
21381    // While executing the command, the language server sends the editor
21382    // a `workspaceEdit` request.
21383    fake_server
21384        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21385            let fake = fake_server.clone();
21386            move |params, _| {
21387                assert_eq!(params.command, "_the/command");
21388                let fake = fake.clone();
21389                async move {
21390                    fake.server
21391                        .request::<lsp::request::ApplyWorkspaceEdit>(
21392                            lsp::ApplyWorkspaceEditParams {
21393                                label: None,
21394                                edit: lsp::WorkspaceEdit {
21395                                    changes: Some(
21396                                        [(
21397                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21398                                            vec![lsp::TextEdit {
21399                                                range: lsp::Range::new(
21400                                                    lsp::Position::new(0, 0),
21401                                                    lsp::Position::new(0, 0),
21402                                                ),
21403                                                new_text: "X".into(),
21404                                            }],
21405                                        )]
21406                                        .into_iter()
21407                                        .collect(),
21408                                    ),
21409                                    ..Default::default()
21410                                },
21411                            },
21412                        )
21413                        .await
21414                        .into_response()
21415                        .unwrap();
21416                    Ok(Some(json!(null)))
21417                }
21418            }
21419        })
21420        .next()
21421        .await;
21422
21423    // Applying the code lens command returns a project transaction containing the edits
21424    // sent by the language server in its `workspaceEdit` request.
21425    let transaction = apply.await.unwrap();
21426    assert!(transaction.0.contains_key(&buffer));
21427    buffer.update(cx, |buffer, cx| {
21428        assert_eq!(buffer.text(), "Xa");
21429        buffer.undo(cx);
21430        assert_eq!(buffer.text(), "a");
21431    });
21432}
21433
21434#[gpui::test]
21435async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21436    init_test(cx, |_| {});
21437
21438    let fs = FakeFs::new(cx.executor());
21439    let main_text = r#"fn main() {
21440println!("1");
21441println!("2");
21442println!("3");
21443println!("4");
21444println!("5");
21445}"#;
21446    let lib_text = "mod foo {}";
21447    fs.insert_tree(
21448        path!("/a"),
21449        json!({
21450            "lib.rs": lib_text,
21451            "main.rs": main_text,
21452        }),
21453    )
21454    .await;
21455
21456    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21457    let (workspace, cx) =
21458        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21459    let worktree_id = workspace.update(cx, |workspace, cx| {
21460        workspace.project().update(cx, |project, cx| {
21461            project.worktrees(cx).next().unwrap().read(cx).id()
21462        })
21463    });
21464
21465    let expected_ranges = vec![
21466        Point::new(0, 0)..Point::new(0, 0),
21467        Point::new(1, 0)..Point::new(1, 1),
21468        Point::new(2, 0)..Point::new(2, 2),
21469        Point::new(3, 0)..Point::new(3, 3),
21470    ];
21471
21472    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21473    let editor_1 = workspace
21474        .update_in(cx, |workspace, window, cx| {
21475            workspace.open_path(
21476                (worktree_id, "main.rs"),
21477                Some(pane_1.downgrade()),
21478                true,
21479                window,
21480                cx,
21481            )
21482        })
21483        .unwrap()
21484        .await
21485        .downcast::<Editor>()
21486        .unwrap();
21487    pane_1.update(cx, |pane, cx| {
21488        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21489        open_editor.update(cx, |editor, cx| {
21490            assert_eq!(
21491                editor.display_text(cx),
21492                main_text,
21493                "Original main.rs text on initial open",
21494            );
21495            assert_eq!(
21496                editor
21497                    .selections
21498                    .all::<Point>(cx)
21499                    .into_iter()
21500                    .map(|s| s.range())
21501                    .collect::<Vec<_>>(),
21502                vec![Point::zero()..Point::zero()],
21503                "Default selections on initial open",
21504            );
21505        })
21506    });
21507    editor_1.update_in(cx, |editor, window, cx| {
21508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21509            s.select_ranges(expected_ranges.clone());
21510        });
21511    });
21512
21513    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21514        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21515    });
21516    let editor_2 = workspace
21517        .update_in(cx, |workspace, window, cx| {
21518            workspace.open_path(
21519                (worktree_id, "main.rs"),
21520                Some(pane_2.downgrade()),
21521                true,
21522                window,
21523                cx,
21524            )
21525        })
21526        .unwrap()
21527        .await
21528        .downcast::<Editor>()
21529        .unwrap();
21530    pane_2.update(cx, |pane, cx| {
21531        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21532        open_editor.update(cx, |editor, cx| {
21533            assert_eq!(
21534                editor.display_text(cx),
21535                main_text,
21536                "Original main.rs text on initial open in another panel",
21537            );
21538            assert_eq!(
21539                editor
21540                    .selections
21541                    .all::<Point>(cx)
21542                    .into_iter()
21543                    .map(|s| s.range())
21544                    .collect::<Vec<_>>(),
21545                vec![Point::zero()..Point::zero()],
21546                "Default selections on initial open in another panel",
21547            );
21548        })
21549    });
21550
21551    editor_2.update_in(cx, |editor, window, cx| {
21552        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21553    });
21554
21555    let _other_editor_1 = workspace
21556        .update_in(cx, |workspace, window, cx| {
21557            workspace.open_path(
21558                (worktree_id, "lib.rs"),
21559                Some(pane_1.downgrade()),
21560                true,
21561                window,
21562                cx,
21563            )
21564        })
21565        .unwrap()
21566        .await
21567        .downcast::<Editor>()
21568        .unwrap();
21569    pane_1
21570        .update_in(cx, |pane, window, cx| {
21571            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21572        })
21573        .await
21574        .unwrap();
21575    drop(editor_1);
21576    pane_1.update(cx, |pane, cx| {
21577        pane.active_item()
21578            .unwrap()
21579            .downcast::<Editor>()
21580            .unwrap()
21581            .update(cx, |editor, cx| {
21582                assert_eq!(
21583                    editor.display_text(cx),
21584                    lib_text,
21585                    "Other file should be open and active",
21586                );
21587            });
21588        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21589    });
21590
21591    let _other_editor_2 = workspace
21592        .update_in(cx, |workspace, window, cx| {
21593            workspace.open_path(
21594                (worktree_id, "lib.rs"),
21595                Some(pane_2.downgrade()),
21596                true,
21597                window,
21598                cx,
21599            )
21600        })
21601        .unwrap()
21602        .await
21603        .downcast::<Editor>()
21604        .unwrap();
21605    pane_2
21606        .update_in(cx, |pane, window, cx| {
21607            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21608        })
21609        .await
21610        .unwrap();
21611    drop(editor_2);
21612    pane_2.update(cx, |pane, cx| {
21613        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21614        open_editor.update(cx, |editor, cx| {
21615            assert_eq!(
21616                editor.display_text(cx),
21617                lib_text,
21618                "Other file should be open and active in another panel too",
21619            );
21620        });
21621        assert_eq!(
21622            pane.items().count(),
21623            1,
21624            "No other editors should be open in another pane",
21625        );
21626    });
21627
21628    let _editor_1_reopened = workspace
21629        .update_in(cx, |workspace, window, cx| {
21630            workspace.open_path(
21631                (worktree_id, "main.rs"),
21632                Some(pane_1.downgrade()),
21633                true,
21634                window,
21635                cx,
21636            )
21637        })
21638        .unwrap()
21639        .await
21640        .downcast::<Editor>()
21641        .unwrap();
21642    let _editor_2_reopened = workspace
21643        .update_in(cx, |workspace, window, cx| {
21644            workspace.open_path(
21645                (worktree_id, "main.rs"),
21646                Some(pane_2.downgrade()),
21647                true,
21648                window,
21649                cx,
21650            )
21651        })
21652        .unwrap()
21653        .await
21654        .downcast::<Editor>()
21655        .unwrap();
21656    pane_1.update(cx, |pane, cx| {
21657        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21658        open_editor.update(cx, |editor, cx| {
21659            assert_eq!(
21660                editor.display_text(cx),
21661                main_text,
21662                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21663            );
21664            assert_eq!(
21665                editor
21666                    .selections
21667                    .all::<Point>(cx)
21668                    .into_iter()
21669                    .map(|s| s.range())
21670                    .collect::<Vec<_>>(),
21671                expected_ranges,
21672                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21673            );
21674        })
21675    });
21676    pane_2.update(cx, |pane, cx| {
21677        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21678        open_editor.update(cx, |editor, cx| {
21679            assert_eq!(
21680                editor.display_text(cx),
21681                r#"fn main() {
21682⋯rintln!("1");
21683⋯intln!("2");
21684⋯ntln!("3");
21685println!("4");
21686println!("5");
21687}"#,
21688                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21689            );
21690            assert_eq!(
21691                editor
21692                    .selections
21693                    .all::<Point>(cx)
21694                    .into_iter()
21695                    .map(|s| s.range())
21696                    .collect::<Vec<_>>(),
21697                vec![Point::zero()..Point::zero()],
21698                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21699            );
21700        })
21701    });
21702}
21703
21704#[gpui::test]
21705async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21706    init_test(cx, |_| {});
21707
21708    let fs = FakeFs::new(cx.executor());
21709    let main_text = r#"fn main() {
21710println!("1");
21711println!("2");
21712println!("3");
21713println!("4");
21714println!("5");
21715}"#;
21716    let lib_text = "mod foo {}";
21717    fs.insert_tree(
21718        path!("/a"),
21719        json!({
21720            "lib.rs": lib_text,
21721            "main.rs": main_text,
21722        }),
21723    )
21724    .await;
21725
21726    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21727    let (workspace, cx) =
21728        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21729    let worktree_id = workspace.update(cx, |workspace, cx| {
21730        workspace.project().update(cx, |project, cx| {
21731            project.worktrees(cx).next().unwrap().read(cx).id()
21732        })
21733    });
21734
21735    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21736    let editor = workspace
21737        .update_in(cx, |workspace, window, cx| {
21738            workspace.open_path(
21739                (worktree_id, "main.rs"),
21740                Some(pane.downgrade()),
21741                true,
21742                window,
21743                cx,
21744            )
21745        })
21746        .unwrap()
21747        .await
21748        .downcast::<Editor>()
21749        .unwrap();
21750    pane.update(cx, |pane, cx| {
21751        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21752        open_editor.update(cx, |editor, cx| {
21753            assert_eq!(
21754                editor.display_text(cx),
21755                main_text,
21756                "Original main.rs text on initial open",
21757            );
21758        })
21759    });
21760    editor.update_in(cx, |editor, window, cx| {
21761        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21762    });
21763
21764    cx.update_global(|store: &mut SettingsStore, cx| {
21765        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21766            s.restore_on_file_reopen = Some(false);
21767        });
21768    });
21769    editor.update_in(cx, |editor, window, cx| {
21770        editor.fold_ranges(
21771            vec![
21772                Point::new(1, 0)..Point::new(1, 1),
21773                Point::new(2, 0)..Point::new(2, 2),
21774                Point::new(3, 0)..Point::new(3, 3),
21775            ],
21776            false,
21777            window,
21778            cx,
21779        );
21780    });
21781    pane.update_in(cx, |pane, window, cx| {
21782        pane.close_all_items(&CloseAllItems::default(), window, cx)
21783    })
21784    .await
21785    .unwrap();
21786    pane.update(cx, |pane, _| {
21787        assert!(pane.active_item().is_none());
21788    });
21789    cx.update_global(|store: &mut SettingsStore, cx| {
21790        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21791            s.restore_on_file_reopen = Some(true);
21792        });
21793    });
21794
21795    let _editor_reopened = workspace
21796        .update_in(cx, |workspace, window, cx| {
21797            workspace.open_path(
21798                (worktree_id, "main.rs"),
21799                Some(pane.downgrade()),
21800                true,
21801                window,
21802                cx,
21803            )
21804        })
21805        .unwrap()
21806        .await
21807        .downcast::<Editor>()
21808        .unwrap();
21809    pane.update(cx, |pane, cx| {
21810        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21811        open_editor.update(cx, |editor, cx| {
21812            assert_eq!(
21813                editor.display_text(cx),
21814                main_text,
21815                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21816            );
21817        })
21818    });
21819}
21820
21821#[gpui::test]
21822async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21823    struct EmptyModalView {
21824        focus_handle: gpui::FocusHandle,
21825    }
21826    impl EventEmitter<DismissEvent> for EmptyModalView {}
21827    impl Render for EmptyModalView {
21828        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21829            div()
21830        }
21831    }
21832    impl Focusable for EmptyModalView {
21833        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21834            self.focus_handle.clone()
21835        }
21836    }
21837    impl workspace::ModalView for EmptyModalView {}
21838    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21839        EmptyModalView {
21840            focus_handle: cx.focus_handle(),
21841        }
21842    }
21843
21844    init_test(cx, |_| {});
21845
21846    let fs = FakeFs::new(cx.executor());
21847    let project = Project::test(fs, [], cx).await;
21848    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21849    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21850    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21851    let editor = cx.new_window_entity(|window, cx| {
21852        Editor::new(
21853            EditorMode::full(),
21854            buffer,
21855            Some(project.clone()),
21856            window,
21857            cx,
21858        )
21859    });
21860    workspace
21861        .update(cx, |workspace, window, cx| {
21862            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21863        })
21864        .unwrap();
21865    editor.update_in(cx, |editor, window, cx| {
21866        editor.open_context_menu(&OpenContextMenu, window, cx);
21867        assert!(editor.mouse_context_menu.is_some());
21868    });
21869    workspace
21870        .update(cx, |workspace, window, cx| {
21871            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21872        })
21873        .unwrap();
21874    cx.read(|cx| {
21875        assert!(editor.read(cx).mouse_context_menu.is_none());
21876    });
21877}
21878
21879#[gpui::test]
21880async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21881    init_test(cx, |_| {});
21882
21883    let fs = FakeFs::new(cx.executor());
21884    fs.insert_file(path!("/file.html"), Default::default())
21885        .await;
21886
21887    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21888
21889    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21890    let html_language = Arc::new(Language::new(
21891        LanguageConfig {
21892            name: "HTML".into(),
21893            matcher: LanguageMatcher {
21894                path_suffixes: vec!["html".to_string()],
21895                ..LanguageMatcher::default()
21896            },
21897            brackets: BracketPairConfig {
21898                pairs: vec![BracketPair {
21899                    start: "<".into(),
21900                    end: ">".into(),
21901                    close: true,
21902                    ..Default::default()
21903                }],
21904                ..Default::default()
21905            },
21906            ..Default::default()
21907        },
21908        Some(tree_sitter_html::LANGUAGE.into()),
21909    ));
21910    language_registry.add(html_language);
21911    let mut fake_servers = language_registry.register_fake_lsp(
21912        "HTML",
21913        FakeLspAdapter {
21914            capabilities: lsp::ServerCapabilities {
21915                completion_provider: Some(lsp::CompletionOptions {
21916                    resolve_provider: Some(true),
21917                    ..Default::default()
21918                }),
21919                ..Default::default()
21920            },
21921            ..Default::default()
21922        },
21923    );
21924
21925    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21926    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21927
21928    let worktree_id = workspace
21929        .update(cx, |workspace, _window, cx| {
21930            workspace.project().update(cx, |project, cx| {
21931                project.worktrees(cx).next().unwrap().read(cx).id()
21932            })
21933        })
21934        .unwrap();
21935    project
21936        .update(cx, |project, cx| {
21937            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21938        })
21939        .await
21940        .unwrap();
21941    let editor = workspace
21942        .update(cx, |workspace, window, cx| {
21943            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21944        })
21945        .unwrap()
21946        .await
21947        .unwrap()
21948        .downcast::<Editor>()
21949        .unwrap();
21950
21951    let fake_server = fake_servers.next().await.unwrap();
21952    editor.update_in(cx, |editor, window, cx| {
21953        editor.set_text("<ad></ad>", window, cx);
21954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21955            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21956        });
21957        let Some((buffer, _)) = editor
21958            .buffer
21959            .read(cx)
21960            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21961        else {
21962            panic!("Failed to get buffer for selection position");
21963        };
21964        let buffer = buffer.read(cx);
21965        let buffer_id = buffer.remote_id();
21966        let opening_range =
21967            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21968        let closing_range =
21969            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21970        let mut linked_ranges = HashMap::default();
21971        linked_ranges.insert(
21972            buffer_id,
21973            vec![(opening_range.clone(), vec![closing_range.clone()])],
21974        );
21975        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21976    });
21977    let mut completion_handle =
21978        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21979            Ok(Some(lsp::CompletionResponse::Array(vec![
21980                lsp::CompletionItem {
21981                    label: "head".to_string(),
21982                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21983                        lsp::InsertReplaceEdit {
21984                            new_text: "head".to_string(),
21985                            insert: lsp::Range::new(
21986                                lsp::Position::new(0, 1),
21987                                lsp::Position::new(0, 3),
21988                            ),
21989                            replace: lsp::Range::new(
21990                                lsp::Position::new(0, 1),
21991                                lsp::Position::new(0, 3),
21992                            ),
21993                        },
21994                    )),
21995                    ..Default::default()
21996                },
21997            ])))
21998        });
21999    editor.update_in(cx, |editor, window, cx| {
22000        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22001    });
22002    cx.run_until_parked();
22003    completion_handle.next().await.unwrap();
22004    editor.update(cx, |editor, _| {
22005        assert!(
22006            editor.context_menu_visible(),
22007            "Completion menu should be visible"
22008        );
22009    });
22010    editor.update_in(cx, |editor, window, cx| {
22011        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22012    });
22013    cx.executor().run_until_parked();
22014    editor.update(cx, |editor, cx| {
22015        assert_eq!(editor.text(cx), "<head></head>");
22016    });
22017}
22018
22019#[gpui::test]
22020async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22021    init_test(cx, |_| {});
22022
22023    let fs = FakeFs::new(cx.executor());
22024    fs.insert_tree(
22025        path!("/root"),
22026        json!({
22027            "a": {
22028                "main.rs": "fn main() {}",
22029            },
22030            "foo": {
22031                "bar": {
22032                    "external_file.rs": "pub mod external {}",
22033                }
22034            }
22035        }),
22036    )
22037    .await;
22038
22039    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22040    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22041    language_registry.add(rust_lang());
22042    let _fake_servers = language_registry.register_fake_lsp(
22043        "Rust",
22044        FakeLspAdapter {
22045            ..FakeLspAdapter::default()
22046        },
22047    );
22048    let (workspace, cx) =
22049        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22050    let worktree_id = workspace.update(cx, |workspace, cx| {
22051        workspace.project().update(cx, |project, cx| {
22052            project.worktrees(cx).next().unwrap().read(cx).id()
22053        })
22054    });
22055
22056    let assert_language_servers_count =
22057        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22058            project.update(cx, |project, cx| {
22059                let current = project
22060                    .lsp_store()
22061                    .read(cx)
22062                    .as_local()
22063                    .unwrap()
22064                    .language_servers
22065                    .len();
22066                assert_eq!(expected, current, "{context}");
22067            });
22068        };
22069
22070    assert_language_servers_count(
22071        0,
22072        "No servers should be running before any file is open",
22073        cx,
22074    );
22075    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22076    let main_editor = workspace
22077        .update_in(cx, |workspace, window, cx| {
22078            workspace.open_path(
22079                (worktree_id, "main.rs"),
22080                Some(pane.downgrade()),
22081                true,
22082                window,
22083                cx,
22084            )
22085        })
22086        .unwrap()
22087        .await
22088        .downcast::<Editor>()
22089        .unwrap();
22090    pane.update(cx, |pane, cx| {
22091        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22092        open_editor.update(cx, |editor, cx| {
22093            assert_eq!(
22094                editor.display_text(cx),
22095                "fn main() {}",
22096                "Original main.rs text on initial open",
22097            );
22098        });
22099        assert_eq!(open_editor, main_editor);
22100    });
22101    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22102
22103    let external_editor = workspace
22104        .update_in(cx, |workspace, window, cx| {
22105            workspace.open_abs_path(
22106                PathBuf::from("/root/foo/bar/external_file.rs"),
22107                OpenOptions::default(),
22108                window,
22109                cx,
22110            )
22111        })
22112        .await
22113        .expect("opening external file")
22114        .downcast::<Editor>()
22115        .expect("downcasted external file's open element to editor");
22116    pane.update(cx, |pane, cx| {
22117        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22118        open_editor.update(cx, |editor, cx| {
22119            assert_eq!(
22120                editor.display_text(cx),
22121                "pub mod external {}",
22122                "External file is open now",
22123            );
22124        });
22125        assert_eq!(open_editor, external_editor);
22126    });
22127    assert_language_servers_count(
22128        1,
22129        "Second, external, *.rs file should join the existing server",
22130        cx,
22131    );
22132
22133    pane.update_in(cx, |pane, window, cx| {
22134        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22135    })
22136    .await
22137    .unwrap();
22138    pane.update_in(cx, |pane, window, cx| {
22139        pane.navigate_backward(window, cx);
22140    });
22141    cx.run_until_parked();
22142    pane.update(cx, |pane, cx| {
22143        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22144        open_editor.update(cx, |editor, cx| {
22145            assert_eq!(
22146                editor.display_text(cx),
22147                "pub mod external {}",
22148                "External file is open now",
22149            );
22150        });
22151    });
22152    assert_language_servers_count(
22153        1,
22154        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22155        cx,
22156    );
22157
22158    cx.update(|_, cx| {
22159        workspace::reload(&workspace::Reload::default(), cx);
22160    });
22161    assert_language_servers_count(
22162        1,
22163        "After reloading the worktree with local and external files opened, only one project should be started",
22164        cx,
22165    );
22166}
22167
22168#[gpui::test]
22169async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22170    init_test(cx, |_| {});
22171
22172    let mut cx = EditorTestContext::new(cx).await;
22173    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22174    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22175
22176    // test cursor move to start of each line on tab
22177    // for `if`, `elif`, `else`, `while`, `with` and `for`
22178    cx.set_state(indoc! {"
22179        def main():
22180        ˇ    for item in items:
22181        ˇ        while item.active:
22182        ˇ            if item.value > 10:
22183        ˇ                continue
22184        ˇ            elif item.value < 0:
22185        ˇ                break
22186        ˇ            else:
22187        ˇ                with item.context() as ctx:
22188        ˇ                    yield count
22189        ˇ        else:
22190        ˇ            log('while else')
22191        ˇ    else:
22192        ˇ        log('for else')
22193    "});
22194    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22195    cx.assert_editor_state(indoc! {"
22196        def main():
22197            ˇfor item in items:
22198                ˇwhile item.active:
22199                    ˇif item.value > 10:
22200                        ˇcontinue
22201                    ˇelif item.value < 0:
22202                        ˇbreak
22203                    ˇelse:
22204                        ˇwith item.context() as ctx:
22205                            ˇyield count
22206                ˇelse:
22207                    ˇlog('while else')
22208            ˇelse:
22209                ˇlog('for else')
22210    "});
22211    // test relative indent is preserved when tab
22212    // for `if`, `elif`, `else`, `while`, `with` and `for`
22213    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22214    cx.assert_editor_state(indoc! {"
22215        def main():
22216                ˇfor item in items:
22217                    ˇwhile item.active:
22218                        ˇif item.value > 10:
22219                            ˇcontinue
22220                        ˇelif item.value < 0:
22221                            ˇbreak
22222                        ˇelse:
22223                            ˇwith item.context() as ctx:
22224                                ˇyield count
22225                    ˇelse:
22226                        ˇlog('while else')
22227                ˇelse:
22228                    ˇlog('for else')
22229    "});
22230
22231    // test cursor move to start of each line on tab
22232    // for `try`, `except`, `else`, `finally`, `match` and `def`
22233    cx.set_state(indoc! {"
22234        def main():
22235        ˇ    try:
22236        ˇ        fetch()
22237        ˇ    except ValueError:
22238        ˇ        handle_error()
22239        ˇ    else:
22240        ˇ        match value:
22241        ˇ            case _:
22242        ˇ    finally:
22243        ˇ        def status():
22244        ˇ            return 0
22245    "});
22246    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22247    cx.assert_editor_state(indoc! {"
22248        def main():
22249            ˇtry:
22250                ˇfetch()
22251            ˇexcept ValueError:
22252                ˇhandle_error()
22253            ˇelse:
22254                ˇmatch value:
22255                    ˇcase _:
22256            ˇfinally:
22257                ˇdef status():
22258                    ˇreturn 0
22259    "});
22260    // test relative indent is preserved when tab
22261    // for `try`, `except`, `else`, `finally`, `match` and `def`
22262    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22263    cx.assert_editor_state(indoc! {"
22264        def main():
22265                ˇtry:
22266                    ˇfetch()
22267                ˇexcept ValueError:
22268                    ˇhandle_error()
22269                ˇelse:
22270                    ˇmatch value:
22271                        ˇcase _:
22272                ˇfinally:
22273                    ˇdef status():
22274                        ˇreturn 0
22275    "});
22276}
22277
22278#[gpui::test]
22279async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22280    init_test(cx, |_| {});
22281
22282    let mut cx = EditorTestContext::new(cx).await;
22283    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22284    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22285
22286    // test `else` auto outdents when typed inside `if` block
22287    cx.set_state(indoc! {"
22288        def main():
22289            if i == 2:
22290                return
22291                ˇ
22292    "});
22293    cx.update_editor(|editor, window, cx| {
22294        editor.handle_input("else:", window, cx);
22295    });
22296    cx.assert_editor_state(indoc! {"
22297        def main():
22298            if i == 2:
22299                return
22300            else:ˇ
22301    "});
22302
22303    // test `except` auto outdents when typed inside `try` block
22304    cx.set_state(indoc! {"
22305        def main():
22306            try:
22307                i = 2
22308                ˇ
22309    "});
22310    cx.update_editor(|editor, window, cx| {
22311        editor.handle_input("except:", window, cx);
22312    });
22313    cx.assert_editor_state(indoc! {"
22314        def main():
22315            try:
22316                i = 2
22317            except:ˇ
22318    "});
22319
22320    // test `else` auto outdents when typed inside `except` block
22321    cx.set_state(indoc! {"
22322        def main():
22323            try:
22324                i = 2
22325            except:
22326                j = 2
22327                ˇ
22328    "});
22329    cx.update_editor(|editor, window, cx| {
22330        editor.handle_input("else:", window, cx);
22331    });
22332    cx.assert_editor_state(indoc! {"
22333        def main():
22334            try:
22335                i = 2
22336            except:
22337                j = 2
22338            else:ˇ
22339    "});
22340
22341    // test `finally` auto outdents when typed inside `else` block
22342    cx.set_state(indoc! {"
22343        def main():
22344            try:
22345                i = 2
22346            except:
22347                j = 2
22348            else:
22349                k = 2
22350                ˇ
22351    "});
22352    cx.update_editor(|editor, window, cx| {
22353        editor.handle_input("finally:", window, cx);
22354    });
22355    cx.assert_editor_state(indoc! {"
22356        def main():
22357            try:
22358                i = 2
22359            except:
22360                j = 2
22361            else:
22362                k = 2
22363            finally:ˇ
22364    "});
22365
22366    // test `else` does not outdents when typed inside `except` block right after for block
22367    cx.set_state(indoc! {"
22368        def main():
22369            try:
22370                i = 2
22371            except:
22372                for i in range(n):
22373                    pass
22374                ˇ
22375    "});
22376    cx.update_editor(|editor, window, cx| {
22377        editor.handle_input("else:", window, cx);
22378    });
22379    cx.assert_editor_state(indoc! {"
22380        def main():
22381            try:
22382                i = 2
22383            except:
22384                for i in range(n):
22385                    pass
22386                else:ˇ
22387    "});
22388
22389    // test `finally` auto outdents when typed inside `else` block right after for block
22390    cx.set_state(indoc! {"
22391        def main():
22392            try:
22393                i = 2
22394            except:
22395                j = 2
22396            else:
22397                for i in range(n):
22398                    pass
22399                ˇ
22400    "});
22401    cx.update_editor(|editor, window, cx| {
22402        editor.handle_input("finally:", window, cx);
22403    });
22404    cx.assert_editor_state(indoc! {"
22405        def main():
22406            try:
22407                i = 2
22408            except:
22409                j = 2
22410            else:
22411                for i in range(n):
22412                    pass
22413            finally:ˇ
22414    "});
22415
22416    // test `except` outdents to inner "try" block
22417    cx.set_state(indoc! {"
22418        def main():
22419            try:
22420                i = 2
22421                if i == 2:
22422                    try:
22423                        i = 3
22424                        ˇ
22425    "});
22426    cx.update_editor(|editor, window, cx| {
22427        editor.handle_input("except:", window, cx);
22428    });
22429    cx.assert_editor_state(indoc! {"
22430        def main():
22431            try:
22432                i = 2
22433                if i == 2:
22434                    try:
22435                        i = 3
22436                    except:ˇ
22437    "});
22438
22439    // test `except` outdents to outer "try" block
22440    cx.set_state(indoc! {"
22441        def main():
22442            try:
22443                i = 2
22444                if i == 2:
22445                    try:
22446                        i = 3
22447                ˇ
22448    "});
22449    cx.update_editor(|editor, window, cx| {
22450        editor.handle_input("except:", window, cx);
22451    });
22452    cx.assert_editor_state(indoc! {"
22453        def main():
22454            try:
22455                i = 2
22456                if i == 2:
22457                    try:
22458                        i = 3
22459            except:ˇ
22460    "});
22461
22462    // test `else` stays at correct indent when typed after `for` block
22463    cx.set_state(indoc! {"
22464        def main():
22465            for i in range(10):
22466                if i == 3:
22467                    break
22468            ˇ
22469    "});
22470    cx.update_editor(|editor, window, cx| {
22471        editor.handle_input("else:", window, cx);
22472    });
22473    cx.assert_editor_state(indoc! {"
22474        def main():
22475            for i in range(10):
22476                if i == 3:
22477                    break
22478            else:ˇ
22479    "});
22480
22481    // test does not outdent on typing after line with square brackets
22482    cx.set_state(indoc! {"
22483        def f() -> list[str]:
22484            ˇ
22485    "});
22486    cx.update_editor(|editor, window, cx| {
22487        editor.handle_input("a", window, cx);
22488    });
22489    cx.assert_editor_state(indoc! {"
22490        def f() -> list[str]:
2249122492    "});
22493
22494    // test does not outdent on typing : after case keyword
22495    cx.set_state(indoc! {"
22496        match 1:
22497            caseˇ
22498    "});
22499    cx.update_editor(|editor, window, cx| {
22500        editor.handle_input(":", window, cx);
22501    });
22502    cx.assert_editor_state(indoc! {"
22503        match 1:
22504            case:ˇ
22505    "});
22506}
22507
22508#[gpui::test]
22509async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22510    init_test(cx, |_| {});
22511    update_test_language_settings(cx, |settings| {
22512        settings.defaults.extend_comment_on_newline = Some(false);
22513    });
22514    let mut cx = EditorTestContext::new(cx).await;
22515    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22516    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22517
22518    // test correct indent after newline on comment
22519    cx.set_state(indoc! {"
22520        # COMMENT:ˇ
22521    "});
22522    cx.update_editor(|editor, window, cx| {
22523        editor.newline(&Newline, window, cx);
22524    });
22525    cx.assert_editor_state(indoc! {"
22526        # COMMENT:
22527        ˇ
22528    "});
22529
22530    // test correct indent after newline in brackets
22531    cx.set_state(indoc! {"
22532        {ˇ}
22533    "});
22534    cx.update_editor(|editor, window, cx| {
22535        editor.newline(&Newline, window, cx);
22536    });
22537    cx.run_until_parked();
22538    cx.assert_editor_state(indoc! {"
22539        {
22540            ˇ
22541        }
22542    "});
22543
22544    cx.set_state(indoc! {"
22545        (ˇ)
22546    "});
22547    cx.update_editor(|editor, window, cx| {
22548        editor.newline(&Newline, window, cx);
22549    });
22550    cx.run_until_parked();
22551    cx.assert_editor_state(indoc! {"
22552        (
22553            ˇ
22554        )
22555    "});
22556
22557    // do not indent after empty lists or dictionaries
22558    cx.set_state(indoc! {"
22559        a = []ˇ
22560    "});
22561    cx.update_editor(|editor, window, cx| {
22562        editor.newline(&Newline, window, cx);
22563    });
22564    cx.run_until_parked();
22565    cx.assert_editor_state(indoc! {"
22566        a = []
22567        ˇ
22568    "});
22569}
22570
22571fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22572    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22573    point..point
22574}
22575
22576#[track_caller]
22577fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22578    let (text, ranges) = marked_text_ranges(marked_text, true);
22579    assert_eq!(editor.text(cx), text);
22580    assert_eq!(
22581        editor.selections.ranges(cx),
22582        ranges,
22583        "Assert selections are {}",
22584        marked_text
22585    );
22586}
22587
22588pub fn handle_signature_help_request(
22589    cx: &mut EditorLspTestContext,
22590    mocked_response: lsp::SignatureHelp,
22591) -> impl Future<Output = ()> + use<> {
22592    let mut request =
22593        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22594            let mocked_response = mocked_response.clone();
22595            async move { Ok(Some(mocked_response)) }
22596        });
22597
22598    async move {
22599        request.next().await;
22600    }
22601}
22602
22603#[track_caller]
22604pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22605    cx.update_editor(|editor, _, _| {
22606        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22607            let entries = menu.entries.borrow();
22608            let entries = entries
22609                .iter()
22610                .map(|entry| entry.string.as_str())
22611                .collect::<Vec<_>>();
22612            assert_eq!(entries, expected);
22613        } else {
22614            panic!("Expected completions menu");
22615        }
22616    });
22617}
22618
22619/// Handle completion request passing a marked string specifying where the completion
22620/// should be triggered from using '|' character, what range should be replaced, and what completions
22621/// should be returned using '<' and '>' to delimit the range.
22622///
22623/// Also see `handle_completion_request_with_insert_and_replace`.
22624#[track_caller]
22625pub fn handle_completion_request(
22626    marked_string: &str,
22627    completions: Vec<&'static str>,
22628    is_incomplete: bool,
22629    counter: Arc<AtomicUsize>,
22630    cx: &mut EditorLspTestContext,
22631) -> impl Future<Output = ()> {
22632    let complete_from_marker: TextRangeMarker = '|'.into();
22633    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22634    let (_, mut marked_ranges) = marked_text_ranges_by(
22635        marked_string,
22636        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22637    );
22638
22639    let complete_from_position =
22640        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22641    let replace_range =
22642        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22643
22644    let mut request =
22645        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22646            let completions = completions.clone();
22647            counter.fetch_add(1, atomic::Ordering::Release);
22648            async move {
22649                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22650                assert_eq!(
22651                    params.text_document_position.position,
22652                    complete_from_position
22653                );
22654                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22655                    is_incomplete: is_incomplete,
22656                    item_defaults: None,
22657                    items: completions
22658                        .iter()
22659                        .map(|completion_text| lsp::CompletionItem {
22660                            label: completion_text.to_string(),
22661                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22662                                range: replace_range,
22663                                new_text: completion_text.to_string(),
22664                            })),
22665                            ..Default::default()
22666                        })
22667                        .collect(),
22668                })))
22669            }
22670        });
22671
22672    async move {
22673        request.next().await;
22674    }
22675}
22676
22677/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22678/// given instead, which also contains an `insert` range.
22679///
22680/// This function uses markers to define ranges:
22681/// - `|` marks the cursor position
22682/// - `<>` marks the replace range
22683/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22684pub fn handle_completion_request_with_insert_and_replace(
22685    cx: &mut EditorLspTestContext,
22686    marked_string: &str,
22687    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22688    counter: Arc<AtomicUsize>,
22689) -> impl Future<Output = ()> {
22690    let complete_from_marker: TextRangeMarker = '|'.into();
22691    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22692    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22693
22694    let (_, mut marked_ranges) = marked_text_ranges_by(
22695        marked_string,
22696        vec![
22697            complete_from_marker.clone(),
22698            replace_range_marker.clone(),
22699            insert_range_marker.clone(),
22700        ],
22701    );
22702
22703    let complete_from_position =
22704        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22705    let replace_range =
22706        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22707
22708    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22709        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22710        _ => lsp::Range {
22711            start: replace_range.start,
22712            end: complete_from_position,
22713        },
22714    };
22715
22716    let mut request =
22717        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22718            let completions = completions.clone();
22719            counter.fetch_add(1, atomic::Ordering::Release);
22720            async move {
22721                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22722                assert_eq!(
22723                    params.text_document_position.position, complete_from_position,
22724                    "marker `|` position doesn't match",
22725                );
22726                Ok(Some(lsp::CompletionResponse::Array(
22727                    completions
22728                        .iter()
22729                        .map(|(label, new_text)| lsp::CompletionItem {
22730                            label: label.to_string(),
22731                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22732                                lsp::InsertReplaceEdit {
22733                                    insert: insert_range,
22734                                    replace: replace_range,
22735                                    new_text: new_text.to_string(),
22736                                },
22737                            )),
22738                            ..Default::default()
22739                        })
22740                        .collect(),
22741                )))
22742            }
22743        });
22744
22745    async move {
22746        request.next().await;
22747    }
22748}
22749
22750fn handle_resolve_completion_request(
22751    cx: &mut EditorLspTestContext,
22752    edits: Option<Vec<(&'static str, &'static str)>>,
22753) -> impl Future<Output = ()> {
22754    let edits = edits.map(|edits| {
22755        edits
22756            .iter()
22757            .map(|(marked_string, new_text)| {
22758                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22759                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22760                lsp::TextEdit::new(replace_range, new_text.to_string())
22761            })
22762            .collect::<Vec<_>>()
22763    });
22764
22765    let mut request =
22766        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22767            let edits = edits.clone();
22768            async move {
22769                Ok(lsp::CompletionItem {
22770                    additional_text_edits: edits,
22771                    ..Default::default()
22772                })
22773            }
22774        });
22775
22776    async move {
22777        request.next().await;
22778    }
22779}
22780
22781pub(crate) fn update_test_language_settings(
22782    cx: &mut TestAppContext,
22783    f: impl Fn(&mut AllLanguageSettingsContent),
22784) {
22785    cx.update(|cx| {
22786        SettingsStore::update_global(cx, |store, cx| {
22787            store.update_user_settings::<AllLanguageSettings>(cx, f);
22788        });
22789    });
22790}
22791
22792pub(crate) fn update_test_project_settings(
22793    cx: &mut TestAppContext,
22794    f: impl Fn(&mut ProjectSettings),
22795) {
22796    cx.update(|cx| {
22797        SettingsStore::update_global(cx, |store, cx| {
22798            store.update_user_settings::<ProjectSettings>(cx, f);
22799        });
22800    });
22801}
22802
22803pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22804    cx.update(|cx| {
22805        assets::Assets.load_test_fonts(cx);
22806        let store = SettingsStore::test(cx);
22807        cx.set_global(store);
22808        theme::init(theme::LoadThemes::JustBase, cx);
22809        release_channel::init(SemanticVersion::default(), cx);
22810        client::init_settings(cx);
22811        language::init(cx);
22812        Project::init_settings(cx);
22813        workspace::init_settings(cx);
22814        crate::init(cx);
22815    });
22816    zlog::init_test();
22817    update_test_language_settings(cx, f);
22818}
22819
22820#[track_caller]
22821fn assert_hunk_revert(
22822    not_reverted_text_with_selections: &str,
22823    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22824    expected_reverted_text_with_selections: &str,
22825    base_text: &str,
22826    cx: &mut EditorLspTestContext,
22827) {
22828    cx.set_state(not_reverted_text_with_selections);
22829    cx.set_head_text(base_text);
22830    cx.executor().run_until_parked();
22831
22832    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22833        let snapshot = editor.snapshot(window, cx);
22834        let reverted_hunk_statuses = snapshot
22835            .buffer_snapshot
22836            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22837            .map(|hunk| hunk.status().kind)
22838            .collect::<Vec<_>>();
22839
22840        editor.git_restore(&Default::default(), window, cx);
22841        reverted_hunk_statuses
22842    });
22843    cx.executor().run_until_parked();
22844    cx.assert_editor_state(expected_reverted_text_with_selections);
22845    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22846}
22847
22848#[gpui::test(iterations = 10)]
22849async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22850    init_test(cx, |_| {});
22851
22852    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22853    let counter = diagnostic_requests.clone();
22854
22855    let fs = FakeFs::new(cx.executor());
22856    fs.insert_tree(
22857        path!("/a"),
22858        json!({
22859            "first.rs": "fn main() { let a = 5; }",
22860            "second.rs": "// Test file",
22861        }),
22862    )
22863    .await;
22864
22865    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22866    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22867    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22868
22869    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22870    language_registry.add(rust_lang());
22871    let mut fake_servers = language_registry.register_fake_lsp(
22872        "Rust",
22873        FakeLspAdapter {
22874            capabilities: lsp::ServerCapabilities {
22875                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22876                    lsp::DiagnosticOptions {
22877                        identifier: None,
22878                        inter_file_dependencies: true,
22879                        workspace_diagnostics: true,
22880                        work_done_progress_options: Default::default(),
22881                    },
22882                )),
22883                ..Default::default()
22884            },
22885            ..Default::default()
22886        },
22887    );
22888
22889    let editor = workspace
22890        .update(cx, |workspace, window, cx| {
22891            workspace.open_abs_path(
22892                PathBuf::from(path!("/a/first.rs")),
22893                OpenOptions::default(),
22894                window,
22895                cx,
22896            )
22897        })
22898        .unwrap()
22899        .await
22900        .unwrap()
22901        .downcast::<Editor>()
22902        .unwrap();
22903    let fake_server = fake_servers.next().await.unwrap();
22904    let server_id = fake_server.server.server_id();
22905    let mut first_request = fake_server
22906        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22907            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22908            let result_id = Some(new_result_id.to_string());
22909            assert_eq!(
22910                params.text_document.uri,
22911                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22912            );
22913            async move {
22914                Ok(lsp::DocumentDiagnosticReportResult::Report(
22915                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22916                        related_documents: None,
22917                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22918                            items: Vec::new(),
22919                            result_id,
22920                        },
22921                    }),
22922                ))
22923            }
22924        });
22925
22926    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22927        project.update(cx, |project, cx| {
22928            let buffer_id = editor
22929                .read(cx)
22930                .buffer()
22931                .read(cx)
22932                .as_singleton()
22933                .expect("created a singleton buffer")
22934                .read(cx)
22935                .remote_id();
22936            let buffer_result_id = project
22937                .lsp_store()
22938                .read(cx)
22939                .result_id(server_id, buffer_id, cx);
22940            assert_eq!(expected, buffer_result_id);
22941        });
22942    };
22943
22944    ensure_result_id(None, cx);
22945    cx.executor().advance_clock(Duration::from_millis(60));
22946    cx.executor().run_until_parked();
22947    assert_eq!(
22948        diagnostic_requests.load(atomic::Ordering::Acquire),
22949        1,
22950        "Opening file should trigger diagnostic request"
22951    );
22952    first_request
22953        .next()
22954        .await
22955        .expect("should have sent the first diagnostics pull request");
22956    ensure_result_id(Some("1".to_string()), cx);
22957
22958    // Editing should trigger diagnostics
22959    editor.update_in(cx, |editor, window, cx| {
22960        editor.handle_input("2", window, cx)
22961    });
22962    cx.executor().advance_clock(Duration::from_millis(60));
22963    cx.executor().run_until_parked();
22964    assert_eq!(
22965        diagnostic_requests.load(atomic::Ordering::Acquire),
22966        2,
22967        "Editing should trigger diagnostic request"
22968    );
22969    ensure_result_id(Some("2".to_string()), cx);
22970
22971    // Moving cursor should not trigger diagnostic request
22972    editor.update_in(cx, |editor, window, cx| {
22973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22974            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22975        });
22976    });
22977    cx.executor().advance_clock(Duration::from_millis(60));
22978    cx.executor().run_until_parked();
22979    assert_eq!(
22980        diagnostic_requests.load(atomic::Ordering::Acquire),
22981        2,
22982        "Cursor movement should not trigger diagnostic request"
22983    );
22984    ensure_result_id(Some("2".to_string()), cx);
22985    // Multiple rapid edits should be debounced
22986    for _ in 0..5 {
22987        editor.update_in(cx, |editor, window, cx| {
22988            editor.handle_input("x", window, cx)
22989        });
22990    }
22991    cx.executor().advance_clock(Duration::from_millis(60));
22992    cx.executor().run_until_parked();
22993
22994    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22995    assert!(
22996        final_requests <= 4,
22997        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22998    );
22999    ensure_result_id(Some(final_requests.to_string()), cx);
23000}
23001
23002#[gpui::test]
23003async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23004    // Regression test for issue #11671
23005    // Previously, adding a cursor after moving multiple cursors would reset
23006    // the cursor count instead of adding to the existing cursors.
23007    init_test(cx, |_| {});
23008    let mut cx = EditorTestContext::new(cx).await;
23009
23010    // Create a simple buffer with cursor at start
23011    cx.set_state(indoc! {"
23012        ˇaaaa
23013        bbbb
23014        cccc
23015        dddd
23016        eeee
23017        ffff
23018        gggg
23019        hhhh"});
23020
23021    // Add 2 cursors below (so we have 3 total)
23022    cx.update_editor(|editor, window, cx| {
23023        editor.add_selection_below(&Default::default(), window, cx);
23024        editor.add_selection_below(&Default::default(), window, cx);
23025    });
23026
23027    // Verify we have 3 cursors
23028    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23029    assert_eq!(
23030        initial_count, 3,
23031        "Should have 3 cursors after adding 2 below"
23032    );
23033
23034    // Move down one line
23035    cx.update_editor(|editor, window, cx| {
23036        editor.move_down(&MoveDown, window, cx);
23037    });
23038
23039    // Add another cursor below
23040    cx.update_editor(|editor, window, cx| {
23041        editor.add_selection_below(&Default::default(), window, cx);
23042    });
23043
23044    // Should now have 4 cursors (3 original + 1 new)
23045    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23046    assert_eq!(
23047        final_count, 4,
23048        "Should have 4 cursors after moving and adding another"
23049    );
23050}
23051
23052#[gpui::test(iterations = 10)]
23053async fn test_document_colors(cx: &mut TestAppContext) {
23054    let expected_color = Rgba {
23055        r: 0.33,
23056        g: 0.33,
23057        b: 0.33,
23058        a: 0.33,
23059    };
23060
23061    init_test(cx, |_| {});
23062
23063    let fs = FakeFs::new(cx.executor());
23064    fs.insert_tree(
23065        path!("/a"),
23066        json!({
23067            "first.rs": "fn main() { let a = 5; }",
23068        }),
23069    )
23070    .await;
23071
23072    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23073    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23074    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23075
23076    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23077    language_registry.add(rust_lang());
23078    let mut fake_servers = language_registry.register_fake_lsp(
23079        "Rust",
23080        FakeLspAdapter {
23081            capabilities: lsp::ServerCapabilities {
23082                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23083                ..lsp::ServerCapabilities::default()
23084            },
23085            name: "rust-analyzer",
23086            ..FakeLspAdapter::default()
23087        },
23088    );
23089    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23090        "Rust",
23091        FakeLspAdapter {
23092            capabilities: lsp::ServerCapabilities {
23093                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23094                ..lsp::ServerCapabilities::default()
23095            },
23096            name: "not-rust-analyzer",
23097            ..FakeLspAdapter::default()
23098        },
23099    );
23100
23101    let editor = workspace
23102        .update(cx, |workspace, window, cx| {
23103            workspace.open_abs_path(
23104                PathBuf::from(path!("/a/first.rs")),
23105                OpenOptions::default(),
23106                window,
23107                cx,
23108            )
23109        })
23110        .unwrap()
23111        .await
23112        .unwrap()
23113        .downcast::<Editor>()
23114        .unwrap();
23115    let fake_language_server = fake_servers.next().await.unwrap();
23116    let fake_language_server_without_capabilities =
23117        fake_servers_without_capabilities.next().await.unwrap();
23118    let requests_made = Arc::new(AtomicUsize::new(0));
23119    let closure_requests_made = Arc::clone(&requests_made);
23120    let mut color_request_handle = fake_language_server
23121        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23122            let requests_made = Arc::clone(&closure_requests_made);
23123            async move {
23124                assert_eq!(
23125                    params.text_document.uri,
23126                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23127                );
23128                requests_made.fetch_add(1, atomic::Ordering::Release);
23129                Ok(vec![
23130                    lsp::ColorInformation {
23131                        range: lsp::Range {
23132                            start: lsp::Position {
23133                                line: 0,
23134                                character: 0,
23135                            },
23136                            end: lsp::Position {
23137                                line: 0,
23138                                character: 1,
23139                            },
23140                        },
23141                        color: lsp::Color {
23142                            red: 0.33,
23143                            green: 0.33,
23144                            blue: 0.33,
23145                            alpha: 0.33,
23146                        },
23147                    },
23148                    lsp::ColorInformation {
23149                        range: lsp::Range {
23150                            start: lsp::Position {
23151                                line: 0,
23152                                character: 0,
23153                            },
23154                            end: lsp::Position {
23155                                line: 0,
23156                                character: 1,
23157                            },
23158                        },
23159                        color: lsp::Color {
23160                            red: 0.33,
23161                            green: 0.33,
23162                            blue: 0.33,
23163                            alpha: 0.33,
23164                        },
23165                    },
23166                ])
23167            }
23168        });
23169
23170    let _handle = fake_language_server_without_capabilities
23171        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23172            panic!("Should not be called");
23173        });
23174    cx.executor().advance_clock(Duration::from_millis(100));
23175    color_request_handle.next().await.unwrap();
23176    cx.run_until_parked();
23177    assert_eq!(
23178        1,
23179        requests_made.load(atomic::Ordering::Acquire),
23180        "Should query for colors once per editor open"
23181    );
23182    editor.update_in(cx, |editor, _, cx| {
23183        assert_eq!(
23184            vec![expected_color],
23185            extract_color_inlays(editor, cx),
23186            "Should have an initial inlay"
23187        );
23188    });
23189
23190    // opening another file in a split should not influence the LSP query counter
23191    workspace
23192        .update(cx, |workspace, window, cx| {
23193            assert_eq!(
23194                workspace.panes().len(),
23195                1,
23196                "Should have one pane with one editor"
23197            );
23198            workspace.move_item_to_pane_in_direction(
23199                &MoveItemToPaneInDirection {
23200                    direction: SplitDirection::Right,
23201                    focus: false,
23202                    clone: true,
23203                },
23204                window,
23205                cx,
23206            );
23207        })
23208        .unwrap();
23209    cx.run_until_parked();
23210    workspace
23211        .update(cx, |workspace, _, cx| {
23212            let panes = workspace.panes();
23213            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23214            for pane in panes {
23215                let editor = pane
23216                    .read(cx)
23217                    .active_item()
23218                    .and_then(|item| item.downcast::<Editor>())
23219                    .expect("Should have opened an editor in each split");
23220                let editor_file = editor
23221                    .read(cx)
23222                    .buffer()
23223                    .read(cx)
23224                    .as_singleton()
23225                    .expect("test deals with singleton buffers")
23226                    .read(cx)
23227                    .file()
23228                    .expect("test buffese should have a file")
23229                    .path();
23230                assert_eq!(
23231                    editor_file.as_ref(),
23232                    Path::new("first.rs"),
23233                    "Both editors should be opened for the same file"
23234                )
23235            }
23236        })
23237        .unwrap();
23238
23239    cx.executor().advance_clock(Duration::from_millis(500));
23240    let save = editor.update_in(cx, |editor, window, cx| {
23241        editor.move_to_end(&MoveToEnd, window, cx);
23242        editor.handle_input("dirty", window, cx);
23243        editor.save(
23244            SaveOptions {
23245                format: true,
23246                autosave: true,
23247            },
23248            project.clone(),
23249            window,
23250            cx,
23251        )
23252    });
23253    save.await.unwrap();
23254
23255    color_request_handle.next().await.unwrap();
23256    cx.run_until_parked();
23257    assert_eq!(
23258        3,
23259        requests_made.load(atomic::Ordering::Acquire),
23260        "Should query for colors once per save and once per formatting after save"
23261    );
23262
23263    drop(editor);
23264    let close = workspace
23265        .update(cx, |workspace, window, cx| {
23266            workspace.active_pane().update(cx, |pane, cx| {
23267                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23268            })
23269        })
23270        .unwrap();
23271    close.await.unwrap();
23272    let close = workspace
23273        .update(cx, |workspace, window, cx| {
23274            workspace.active_pane().update(cx, |pane, cx| {
23275                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23276            })
23277        })
23278        .unwrap();
23279    close.await.unwrap();
23280    assert_eq!(
23281        3,
23282        requests_made.load(atomic::Ordering::Acquire),
23283        "After saving and closing all editors, no extra requests should be made"
23284    );
23285    workspace
23286        .update(cx, |workspace, _, cx| {
23287            assert!(
23288                workspace.active_item(cx).is_none(),
23289                "Should close all editors"
23290            )
23291        })
23292        .unwrap();
23293
23294    workspace
23295        .update(cx, |workspace, window, cx| {
23296            workspace.active_pane().update(cx, |pane, cx| {
23297                pane.navigate_backward(window, cx);
23298            })
23299        })
23300        .unwrap();
23301    cx.executor().advance_clock(Duration::from_millis(100));
23302    cx.run_until_parked();
23303    let editor = workspace
23304        .update(cx, |workspace, _, cx| {
23305            workspace
23306                .active_item(cx)
23307                .expect("Should have reopened the editor again after navigating back")
23308                .downcast::<Editor>()
23309                .expect("Should be an editor")
23310        })
23311        .unwrap();
23312    color_request_handle.next().await.unwrap();
23313    assert_eq!(
23314        3,
23315        requests_made.load(atomic::Ordering::Acquire),
23316        "Cache should be reused on buffer close and reopen"
23317    );
23318    editor.update(cx, |editor, cx| {
23319        assert_eq!(
23320            vec![expected_color],
23321            extract_color_inlays(editor, cx),
23322            "Should have an initial inlay"
23323        );
23324    });
23325}
23326
23327#[gpui::test]
23328async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23329    init_test(cx, |_| {});
23330    let (editor, cx) = cx.add_window_view(Editor::single_line);
23331    editor.update_in(cx, |editor, window, cx| {
23332        editor.set_text("oops\n\nwow\n", window, cx)
23333    });
23334    cx.run_until_parked();
23335    editor.update(cx, |editor, cx| {
23336        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23337    });
23338    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23339    cx.run_until_parked();
23340    editor.update(cx, |editor, cx| {
23341        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23342    });
23343}
23344
23345#[track_caller]
23346fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23347    editor
23348        .all_inlays(cx)
23349        .into_iter()
23350        .filter_map(|inlay| inlay.get_color())
23351        .map(Rgba::from)
23352        .collect()
23353}