editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3089        let mut editor = build_editor(buffer.clone(), window, cx);
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_ranges([3..4, 11..12, 19..20])
 3092        });
 3093        editor
 3094    });
 3095
 3096    _ = editor.update(cx, |editor, window, cx| {
 3097        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3098        editor.buffer.update(cx, |buffer, cx| {
 3099            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3100            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3101        });
 3102        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3103
 3104        editor.insert("Z", window, cx);
 3105        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3106
 3107        // The selections are moved after the inserted characters
 3108        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3109    });
 3110}
 3111
 3112#[gpui::test]
 3113async fn test_tab(cx: &mut TestAppContext) {
 3114    init_test(cx, |settings| {
 3115        settings.defaults.tab_size = NonZeroU32::new(3)
 3116    });
 3117
 3118    let mut cx = EditorTestContext::new(cx).await;
 3119    cx.set_state(indoc! {"
 3120        ˇabˇc
 3121        ˇ🏀ˇ🏀ˇefg
 3122 3123    "});
 3124    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126           ˇab ˇc
 3127           ˇ🏀  ˇ🏀  ˇefg
 3128        d  ˇ
 3129    "});
 3130
 3131    cx.set_state(indoc! {"
 3132        a
 3133        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3134    "});
 3135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        a
 3138           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3144    init_test(cx, |_| {});
 3145
 3146    let mut cx = EditorTestContext::new(cx).await;
 3147    let language = Arc::new(
 3148        Language::new(
 3149            LanguageConfig::default(),
 3150            Some(tree_sitter_rust::LANGUAGE.into()),
 3151        )
 3152        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3153        .unwrap(),
 3154    );
 3155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3156
 3157    // test when all cursors are not at suggested indent
 3158    // then simply move to their suggested indent location
 3159    cx.set_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162        ˇ
 3163        ˇ    )
 3164        );
 3165    "});
 3166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3167    cx.assert_editor_state(indoc! {"
 3168        const a: B = (
 3169            c(
 3170                ˇ
 3171            ˇ)
 3172        );
 3173    "});
 3174
 3175    // test cursor already at suggested indent not moving when
 3176    // other cursors are yet to reach their suggested indents
 3177    cx.set_state(indoc! {"
 3178        ˇ
 3179        const a: B = (
 3180            c(
 3181                d(
 3182        ˇ
 3183                )
 3184        ˇ
 3185        ˇ    )
 3186        );
 3187    "});
 3188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3189    cx.assert_editor_state(indoc! {"
 3190        ˇ
 3191        const a: B = (
 3192            c(
 3193                d(
 3194                    ˇ
 3195                )
 3196                ˇ
 3197            ˇ)
 3198        );
 3199    "});
 3200    // test when all cursors are at suggested indent then tab is inserted
 3201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203            ˇ
 3204        const a: B = (
 3205            c(
 3206                d(
 3207                        ˇ
 3208                )
 3209                    ˇ
 3210                ˇ)
 3211        );
 3212    "});
 3213
 3214    // test when current indent is less than suggested indent,
 3215    // we adjust line to match suggested indent and move cursor to it
 3216    //
 3217    // when no other cursor is at word boundary, all of them should move
 3218    cx.set_state(indoc! {"
 3219        const a: B = (
 3220            c(
 3221                d(
 3222        ˇ
 3223        ˇ   )
 3224        ˇ   )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        const a: B = (
 3230            c(
 3231                d(
 3232                    ˇ
 3233                ˇ)
 3234            ˇ)
 3235        );
 3236    "});
 3237
 3238    // test when current indent is less than suggested indent,
 3239    // we adjust line to match suggested indent and move cursor to it
 3240    //
 3241    // when some other cursor is at word boundary, it should not move
 3242    cx.set_state(indoc! {"
 3243        const a: B = (
 3244            c(
 3245                d(
 3246        ˇ
 3247        ˇ   )
 3248           ˇ)
 3249        );
 3250    "});
 3251    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3252    cx.assert_editor_state(indoc! {"
 3253        const a: B = (
 3254            c(
 3255                d(
 3256                    ˇ
 3257                ˇ)
 3258            ˇ)
 3259        );
 3260    "});
 3261
 3262    // test when current indent is more than suggested indent,
 3263    // we just move cursor to current indent instead of suggested indent
 3264    //
 3265    // when no other cursor is at word boundary, all of them should move
 3266    cx.set_state(indoc! {"
 3267        const a: B = (
 3268            c(
 3269                d(
 3270        ˇ
 3271        ˇ                )
 3272        ˇ   )
 3273        );
 3274    "});
 3275    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3276    cx.assert_editor_state(indoc! {"
 3277        const a: B = (
 3278            c(
 3279                d(
 3280                    ˇ
 3281                        ˇ)
 3282            ˇ)
 3283        );
 3284    "});
 3285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290                        ˇ
 3291                            ˇ)
 3292                ˇ)
 3293        );
 3294    "});
 3295
 3296    // test when current indent is more than suggested indent,
 3297    // we just move cursor to current indent instead of suggested indent
 3298    //
 3299    // when some other cursor is at word boundary, it doesn't move
 3300    cx.set_state(indoc! {"
 3301        const a: B = (
 3302            c(
 3303                d(
 3304        ˇ
 3305        ˇ                )
 3306            ˇ)
 3307        );
 3308    "});
 3309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3310    cx.assert_editor_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314                    ˇ
 3315                        ˇ)
 3316            ˇ)
 3317        );
 3318    "});
 3319
 3320    // handle auto-indent when there are multiple cursors on the same line
 3321    cx.set_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324        ˇ    ˇ
 3325        ˇ    )
 3326        );
 3327    "});
 3328    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        const a: B = (
 3331            c(
 3332                ˇ
 3333            ˇ)
 3334        );
 3335    "});
 3336}
 3337
 3338#[gpui::test]
 3339async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3340    init_test(cx, |settings| {
 3341        settings.defaults.tab_size = NonZeroU32::new(3)
 3342    });
 3343
 3344    let mut cx = EditorTestContext::new(cx).await;
 3345    cx.set_state(indoc! {"
 3346         ˇ
 3347        \t ˇ
 3348        \t  ˇ
 3349        \t   ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3351    "});
 3352
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355           ˇ
 3356        \t   ˇ
 3357        \t   ˇ
 3358        \t      ˇ
 3359         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3360    "});
 3361}
 3362
 3363#[gpui::test]
 3364async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3365    init_test(cx, |settings| {
 3366        settings.defaults.tab_size = NonZeroU32::new(4)
 3367    });
 3368
 3369    let language = Arc::new(
 3370        Language::new(
 3371            LanguageConfig::default(),
 3372            Some(tree_sitter_rust::LANGUAGE.into()),
 3373        )
 3374        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3375        .unwrap(),
 3376    );
 3377
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3380    cx.set_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383        \t ˇc
 3384            }
 3385        }
 3386    "});
 3387
 3388    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3389    cx.assert_editor_state(indoc! {"
 3390        fn a() {
 3391            if b {
 3392                ˇc
 3393            }
 3394        }
 3395    "});
 3396}
 3397
 3398#[gpui::test]
 3399async fn test_indent_outdent(cx: &mut TestAppContext) {
 3400    init_test(cx, |settings| {
 3401        settings.defaults.tab_size = NonZeroU32::new(4);
 3402    });
 3403
 3404    let mut cx = EditorTestContext::new(cx).await;
 3405
 3406    cx.set_state(indoc! {"
 3407          «oneˇ» «twoˇ»
 3408        three
 3409         four
 3410    "});
 3411    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3412    cx.assert_editor_state(indoc! {"
 3413            «oneˇ» «twoˇ»
 3414        three
 3415         four
 3416    "});
 3417
 3418    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        «oneˇ» «twoˇ»
 3421        three
 3422         four
 3423    "});
 3424
 3425    // select across line ending
 3426    cx.set_state(indoc! {"
 3427        one two
 3428        t«hree
 3429        ˇ» four
 3430    "});
 3431    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3432    cx.assert_editor_state(indoc! {"
 3433        one two
 3434            t«hree
 3435        ˇ» four
 3436    "});
 3437
 3438    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3439    cx.assert_editor_state(indoc! {"
 3440        one two
 3441        t«hree
 3442        ˇ» four
 3443    "});
 3444
 3445    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3446    cx.set_state(indoc! {"
 3447        one two
 3448        ˇthree
 3449            four
 3450    "});
 3451    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3452    cx.assert_editor_state(indoc! {"
 3453        one two
 3454            ˇthree
 3455            four
 3456    "});
 3457
 3458    cx.set_state(indoc! {"
 3459        one two
 3460        ˇ    three
 3461            four
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        one two
 3466        ˇthree
 3467            four
 3468    "});
 3469}
 3470
 3471#[gpui::test]
 3472async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3473    init_test(cx, |settings| {
 3474        settings.defaults.hard_tabs = Some(true);
 3475    });
 3476
 3477    let mut cx = EditorTestContext::new(cx).await;
 3478
 3479    // select two ranges on one line
 3480    cx.set_state(indoc! {"
 3481        «oneˇ» «twoˇ»
 3482        three
 3483        four
 3484    "});
 3485    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3486    cx.assert_editor_state(indoc! {"
 3487        \t«oneˇ» «twoˇ»
 3488        three
 3489        four
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        \t\t«oneˇ» «twoˇ»
 3494        three
 3495        four
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        \t«oneˇ» «twoˇ»
 3500        three
 3501        four
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        «oneˇ» «twoˇ»
 3506        three
 3507        four
 3508    "});
 3509
 3510    // select across a line ending
 3511    cx.set_state(indoc! {"
 3512        one two
 3513        t«hree
 3514        ˇ»four
 3515    "});
 3516    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3517    cx.assert_editor_state(indoc! {"
 3518        one two
 3519        \tt«hree
 3520        ˇ»four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525        \t\tt«hree
 3526        ˇ»four
 3527    "});
 3528    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3529    cx.assert_editor_state(indoc! {"
 3530        one two
 3531        \tt«hree
 3532        ˇ»four
 3533    "});
 3534    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3535    cx.assert_editor_state(indoc! {"
 3536        one two
 3537        t«hree
 3538        ˇ»four
 3539    "});
 3540
 3541    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3542    cx.set_state(indoc! {"
 3543        one two
 3544        ˇthree
 3545        four
 3546    "});
 3547    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3548    cx.assert_editor_state(indoc! {"
 3549        one two
 3550        ˇthree
 3551        four
 3552    "});
 3553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3554    cx.assert_editor_state(indoc! {"
 3555        one two
 3556        \tˇthree
 3557        four
 3558    "});
 3559    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3560    cx.assert_editor_state(indoc! {"
 3561        one two
 3562        ˇthree
 3563        four
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3569    init_test(cx, |settings| {
 3570        settings.languages.extend([
 3571            (
 3572                "TOML".into(),
 3573                LanguageSettingsContent {
 3574                    tab_size: NonZeroU32::new(2),
 3575                    ..Default::default()
 3576                },
 3577            ),
 3578            (
 3579                "Rust".into(),
 3580                LanguageSettingsContent {
 3581                    tab_size: NonZeroU32::new(4),
 3582                    ..Default::default()
 3583                },
 3584            ),
 3585        ]);
 3586    });
 3587
 3588    let toml_language = Arc::new(Language::new(
 3589        LanguageConfig {
 3590            name: "TOML".into(),
 3591            ..Default::default()
 3592        },
 3593        None,
 3594    ));
 3595    let rust_language = Arc::new(Language::new(
 3596        LanguageConfig {
 3597            name: "Rust".into(),
 3598            ..Default::default()
 3599        },
 3600        None,
 3601    ));
 3602
 3603    let toml_buffer =
 3604        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3605    let rust_buffer =
 3606        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3607    let multibuffer = cx.new(|cx| {
 3608        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3609        multibuffer.push_excerpts(
 3610            toml_buffer.clone(),
 3611            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3612            cx,
 3613        );
 3614        multibuffer.push_excerpts(
 3615            rust_buffer.clone(),
 3616            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3617            cx,
 3618        );
 3619        multibuffer
 3620    });
 3621
 3622    cx.add_window(|window, cx| {
 3623        let mut editor = build_editor(multibuffer, window, cx);
 3624
 3625        assert_eq!(
 3626            editor.text(cx),
 3627            indoc! {"
 3628                a = 1
 3629                b = 2
 3630
 3631                const c: usize = 3;
 3632            "}
 3633        );
 3634
 3635        select_ranges(
 3636            &mut editor,
 3637            indoc! {"
 3638                «aˇ» = 1
 3639                b = 2
 3640
 3641                «const c:ˇ» usize = 3;
 3642            "},
 3643            window,
 3644            cx,
 3645        );
 3646
 3647        editor.tab(&Tab, window, cx);
 3648        assert_text_with_selections(
 3649            &mut editor,
 3650            indoc! {"
 3651                  «aˇ» = 1
 3652                b = 2
 3653
 3654                    «const c:ˇ» usize = 3;
 3655            "},
 3656            cx,
 3657        );
 3658        editor.backtab(&Backtab, window, cx);
 3659        assert_text_with_selections(
 3660            &mut editor,
 3661            indoc! {"
 3662                «aˇ» = 1
 3663                b = 2
 3664
 3665                «const c:ˇ» usize = 3;
 3666            "},
 3667            cx,
 3668        );
 3669
 3670        editor
 3671    });
 3672}
 3673
 3674#[gpui::test]
 3675async fn test_backspace(cx: &mut TestAppContext) {
 3676    init_test(cx, |_| {});
 3677
 3678    let mut cx = EditorTestContext::new(cx).await;
 3679
 3680    // Basic backspace
 3681    cx.set_state(indoc! {"
 3682        onˇe two three
 3683        fou«rˇ» five six
 3684        seven «ˇeight nine
 3685        »ten
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        oˇe two three
 3690        fouˇ five six
 3691        seven ˇten
 3692    "});
 3693
 3694    // Test backspace inside and around indents
 3695    cx.set_state(indoc! {"
 3696        zero
 3697            ˇone
 3698                ˇtwo
 3699            ˇ ˇ ˇ  three
 3700        ˇ  ˇ  four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        zero
 3705        ˇone
 3706            ˇtwo
 3707        ˇ  threeˇ  four
 3708    "});
 3709}
 3710
 3711#[gpui::test]
 3712async fn test_delete(cx: &mut TestAppContext) {
 3713    init_test(cx, |_| {});
 3714
 3715    let mut cx = EditorTestContext::new(cx).await;
 3716    cx.set_state(indoc! {"
 3717        onˇe two three
 3718        fou«rˇ» five six
 3719        seven «ˇeight nine
 3720        »ten
 3721    "});
 3722    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3723    cx.assert_editor_state(indoc! {"
 3724        onˇ two three
 3725        fouˇ five six
 3726        seven ˇten
 3727    "});
 3728}
 3729
 3730#[gpui::test]
 3731fn test_delete_line(cx: &mut TestAppContext) {
 3732    init_test(cx, |_| {});
 3733
 3734    let editor = cx.add_window(|window, cx| {
 3735        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3736        build_editor(buffer, window, cx)
 3737    });
 3738    _ = editor.update(cx, |editor, window, cx| {
 3739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3740            s.select_display_ranges([
 3741                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3742                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3743                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3744            ])
 3745        });
 3746        editor.delete_line(&DeleteLine, window, cx);
 3747        assert_eq!(editor.display_text(cx), "ghi");
 3748        assert_eq!(
 3749            editor.selections.display_ranges(cx),
 3750            vec![
 3751                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3752                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3753            ]
 3754        );
 3755    });
 3756
 3757    let editor = cx.add_window(|window, cx| {
 3758        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3759        build_editor(buffer, window, cx)
 3760    });
 3761    _ = editor.update(cx, |editor, window, cx| {
 3762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3763            s.select_display_ranges([
 3764                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3765            ])
 3766        });
 3767        editor.delete_line(&DeleteLine, window, cx);
 3768        assert_eq!(editor.display_text(cx), "ghi\n");
 3769        assert_eq!(
 3770            editor.selections.display_ranges(cx),
 3771            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3772        );
 3773    });
 3774}
 3775
 3776#[gpui::test]
 3777fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3778    init_test(cx, |_| {});
 3779
 3780    cx.add_window(|window, cx| {
 3781        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3782        let mut editor = build_editor(buffer.clone(), window, cx);
 3783        let buffer = buffer.read(cx).as_singleton().unwrap();
 3784
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 0)..Point::new(0, 0)]
 3788        );
 3789
 3790        // When on single line, replace newline at end by space
 3791        editor.join_lines(&JoinLines, window, cx);
 3792        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3793        assert_eq!(
 3794            editor.selections.ranges::<Point>(cx),
 3795            &[Point::new(0, 3)..Point::new(0, 3)]
 3796        );
 3797
 3798        // When multiple lines are selected, remove newlines that are spanned by the selection
 3799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3800            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3801        });
 3802        editor.join_lines(&JoinLines, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            &[Point::new(0, 11)..Point::new(0, 11)]
 3807        );
 3808
 3809        // Undo should be transactional
 3810        editor.undo(&Undo, window, cx);
 3811        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3812        assert_eq!(
 3813            editor.selections.ranges::<Point>(cx),
 3814            &[Point::new(0, 5)..Point::new(2, 2)]
 3815        );
 3816
 3817        // When joining an empty line don't insert a space
 3818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3819            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3820        });
 3821        editor.join_lines(&JoinLines, window, cx);
 3822        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3823        assert_eq!(
 3824            editor.selections.ranges::<Point>(cx),
 3825            [Point::new(2, 3)..Point::new(2, 3)]
 3826        );
 3827
 3828        // We can remove trailing newlines
 3829        editor.join_lines(&JoinLines, window, cx);
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3831        assert_eq!(
 3832            editor.selections.ranges::<Point>(cx),
 3833            [Point::new(2, 3)..Point::new(2, 3)]
 3834        );
 3835
 3836        // We don't blow up on the last line
 3837        editor.join_lines(&JoinLines, window, cx);
 3838        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3839        assert_eq!(
 3840            editor.selections.ranges::<Point>(cx),
 3841            [Point::new(2, 3)..Point::new(2, 3)]
 3842        );
 3843
 3844        // reset to test indentation
 3845        editor.buffer.update(cx, |buffer, cx| {
 3846            buffer.edit(
 3847                [
 3848                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3849                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3850                ],
 3851                None,
 3852                cx,
 3853            )
 3854        });
 3855
 3856        // We remove any leading spaces
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3859            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3860        });
 3861        editor.join_lines(&JoinLines, window, cx);
 3862        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3863
 3864        // We don't insert a space for a line containing only spaces
 3865        editor.join_lines(&JoinLines, window, cx);
 3866        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3867
 3868        // We ignore any leading tabs
 3869        editor.join_lines(&JoinLines, window, cx);
 3870        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3871
 3872        editor
 3873    });
 3874}
 3875
 3876#[gpui::test]
 3877fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3878    init_test(cx, |_| {});
 3879
 3880    cx.add_window(|window, cx| {
 3881        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3882        let mut editor = build_editor(buffer.clone(), window, cx);
 3883        let buffer = buffer.read(cx).as_singleton().unwrap();
 3884
 3885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3886            s.select_ranges([
 3887                Point::new(0, 2)..Point::new(1, 1),
 3888                Point::new(1, 2)..Point::new(1, 2),
 3889                Point::new(3, 1)..Point::new(3, 2),
 3890            ])
 3891        });
 3892
 3893        editor.join_lines(&JoinLines, window, cx);
 3894        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3895
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            [
 3899                Point::new(0, 7)..Point::new(0, 7),
 3900                Point::new(1, 3)..Point::new(1, 3)
 3901            ]
 3902        );
 3903        editor
 3904    });
 3905}
 3906
 3907#[gpui::test]
 3908async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3909    init_test(cx, |_| {});
 3910
 3911    let mut cx = EditorTestContext::new(cx).await;
 3912
 3913    let diff_base = r#"
 3914        Line 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919    .unindent();
 3920
 3921    cx.set_state(
 3922        &r#"
 3923        ˇLine 0
 3924        Line 1
 3925        Line 2
 3926        Line 3
 3927        "#
 3928        .unindent(),
 3929    );
 3930
 3931    cx.set_head_text(&diff_base);
 3932    executor.run_until_parked();
 3933
 3934    // Join lines
 3935    cx.update_editor(|editor, window, cx| {
 3936        editor.join_lines(&JoinLines, window, cx);
 3937    });
 3938    executor.run_until_parked();
 3939
 3940    cx.assert_editor_state(
 3941        &r#"
 3942        Line 0ˇ Line 1
 3943        Line 2
 3944        Line 3
 3945        "#
 3946        .unindent(),
 3947    );
 3948    // Join again
 3949    cx.update_editor(|editor, window, cx| {
 3950        editor.join_lines(&JoinLines, window, cx);
 3951    });
 3952    executor.run_until_parked();
 3953
 3954    cx.assert_editor_state(
 3955        &r#"
 3956        Line 0 Line 1ˇ Line 2
 3957        Line 3
 3958        "#
 3959        .unindent(),
 3960    );
 3961}
 3962
 3963#[gpui::test]
 3964async fn test_custom_newlines_cause_no_false_positive_diffs(
 3965    executor: BackgroundExecutor,
 3966    cx: &mut TestAppContext,
 3967) {
 3968    init_test(cx, |_| {});
 3969    let mut cx = EditorTestContext::new(cx).await;
 3970    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3971    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3972    executor.run_until_parked();
 3973
 3974    cx.update_editor(|editor, window, cx| {
 3975        let snapshot = editor.snapshot(window, cx);
 3976        assert_eq!(
 3977            snapshot
 3978                .buffer_snapshot
 3979                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3980                .collect::<Vec<_>>(),
 3981            Vec::new(),
 3982            "Should not have any diffs for files with custom newlines"
 3983        );
 3984    });
 3985}
 3986
 3987#[gpui::test]
 3988async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 3989    init_test(cx, |_| {});
 3990
 3991    let mut cx = EditorTestContext::new(cx).await;
 3992
 3993    // Test sort_lines_case_insensitive()
 3994    cx.set_state(indoc! {"
 3995        «z
 3996        y
 3997        x
 3998        Z
 3999        Y
 4000        Xˇ»
 4001    "});
 4002    cx.update_editor(|e, window, cx| {
 4003        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4004    });
 4005    cx.assert_editor_state(indoc! {"
 4006        «x
 4007        X
 4008        y
 4009        Y
 4010        z
 4011        Zˇ»
 4012    "});
 4013
 4014    // Test reverse_lines()
 4015    cx.set_state(indoc! {"
 4016        «5
 4017        4
 4018        3
 4019        2
 4020        1ˇ»
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        «1
 4025        2
 4026        3
 4027        4
 4028        5ˇ»
 4029    "});
 4030
 4031    // Skip testing shuffle_line()
 4032
 4033    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4034    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4035
 4036    // Don't manipulate when cursor is on single line, but expand the selection
 4037    cx.set_state(indoc! {"
 4038        ddˇdd
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043    cx.update_editor(|e, window, cx| {
 4044        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4045    });
 4046    cx.assert_editor_state(indoc! {"
 4047        «ddddˇ»
 4048        ccc
 4049        bb
 4050        a
 4051    "});
 4052
 4053    // Basic manipulate case
 4054    // Start selection moves to column 0
 4055    // End of selection shrinks to fit shorter line
 4056    cx.set_state(indoc! {"
 4057        dd«d
 4058        ccc
 4059        bb
 4060        aaaaaˇ»
 4061    "});
 4062    cx.update_editor(|e, window, cx| {
 4063        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4064    });
 4065    cx.assert_editor_state(indoc! {"
 4066        «aaaaa
 4067        bb
 4068        ccc
 4069        dddˇ»
 4070    "});
 4071
 4072    // Manipulate case with newlines
 4073    cx.set_state(indoc! {"
 4074        dd«d
 4075        ccc
 4076
 4077        bb
 4078        aaaaa
 4079
 4080        ˇ»
 4081    "});
 4082    cx.update_editor(|e, window, cx| {
 4083        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4084    });
 4085    cx.assert_editor_state(indoc! {"
 4086        «
 4087
 4088        aaaaa
 4089        bb
 4090        ccc
 4091        dddˇ»
 4092
 4093    "});
 4094
 4095    // Adding new line
 4096    cx.set_state(indoc! {"
 4097        aa«a
 4098        bbˇ»b
 4099    "});
 4100    cx.update_editor(|e, window, cx| {
 4101        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4102    });
 4103    cx.assert_editor_state(indoc! {"
 4104        «aaa
 4105        bbb
 4106        added_lineˇ»
 4107    "});
 4108
 4109    // Removing line
 4110    cx.set_state(indoc! {"
 4111        aa«a
 4112        bbbˇ»
 4113    "});
 4114    cx.update_editor(|e, window, cx| {
 4115        e.manipulate_immutable_lines(window, cx, |lines| {
 4116            lines.pop();
 4117        })
 4118    });
 4119    cx.assert_editor_state(indoc! {"
 4120        «aaaˇ»
 4121    "});
 4122
 4123    // Removing all lines
 4124    cx.set_state(indoc! {"
 4125        aa«a
 4126        bbbˇ»
 4127    "});
 4128    cx.update_editor(|e, window, cx| {
 4129        e.manipulate_immutable_lines(window, cx, |lines| {
 4130            lines.drain(..);
 4131        })
 4132    });
 4133    cx.assert_editor_state(indoc! {"
 4134        ˇ
 4135    "});
 4136}
 4137
 4138#[gpui::test]
 4139async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4140    init_test(cx, |_| {});
 4141
 4142    let mut cx = EditorTestContext::new(cx).await;
 4143
 4144    // Consider continuous selection as single selection
 4145    cx.set_state(indoc! {"
 4146        Aaa«aa
 4147        cˇ»c«c
 4148        bb
 4149        aaaˇ»aa
 4150    "});
 4151    cx.update_editor(|e, window, cx| {
 4152        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4153    });
 4154    cx.assert_editor_state(indoc! {"
 4155        «Aaaaa
 4156        ccc
 4157        bb
 4158        aaaaaˇ»
 4159    "});
 4160
 4161    cx.set_state(indoc! {"
 4162        Aaa«aa
 4163        cˇ»c«c
 4164        bb
 4165        aaaˇ»aa
 4166    "});
 4167    cx.update_editor(|e, window, cx| {
 4168        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4169    });
 4170    cx.assert_editor_state(indoc! {"
 4171        «Aaaaa
 4172        ccc
 4173        bbˇ»
 4174    "});
 4175
 4176    // Consider non continuous selection as distinct dedup operations
 4177    cx.set_state(indoc! {"
 4178        «aaaaa
 4179        bb
 4180        aaaaa
 4181        aaaaaˇ»
 4182
 4183        aaa«aaˇ»
 4184    "});
 4185    cx.update_editor(|e, window, cx| {
 4186        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4187    });
 4188    cx.assert_editor_state(indoc! {"
 4189        «aaaaa
 4190        bbˇ»
 4191
 4192        «aaaaaˇ»
 4193    "});
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4198    init_test(cx, |_| {});
 4199
 4200    let mut cx = EditorTestContext::new(cx).await;
 4201
 4202    cx.set_state(indoc! {"
 4203        «Aaa
 4204        aAa
 4205        Aaaˇ»
 4206    "});
 4207    cx.update_editor(|e, window, cx| {
 4208        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4209    });
 4210    cx.assert_editor_state(indoc! {"
 4211        «Aaa
 4212        aAaˇ»
 4213    "});
 4214
 4215    cx.set_state(indoc! {"
 4216        «Aaa
 4217        aAa
 4218        aaAˇ»
 4219    "});
 4220    cx.update_editor(|e, window, cx| {
 4221        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4222    });
 4223    cx.assert_editor_state(indoc! {"
 4224        «Aaaˇ»
 4225    "});
 4226}
 4227
 4228#[gpui::test]
 4229async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4230    init_test(cx, |_| {});
 4231
 4232    let mut cx = EditorTestContext::new(cx).await;
 4233
 4234    // Manipulate with multiple selections on a single line
 4235    cx.set_state(indoc! {"
 4236        dd«dd
 4237        cˇ»c«c
 4238        bb
 4239        aaaˇ»aa
 4240    "});
 4241    cx.update_editor(|e, window, cx| {
 4242        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4243    });
 4244    cx.assert_editor_state(indoc! {"
 4245        «aaaaa
 4246        bb
 4247        ccc
 4248        ddddˇ»
 4249    "});
 4250
 4251    // Manipulate with multiple disjoin selections
 4252    cx.set_state(indoc! {"
 4253 4254        4
 4255        3
 4256        2
 4257        1ˇ»
 4258
 4259        dd«dd
 4260        ccc
 4261        bb
 4262        aaaˇ»aa
 4263    "});
 4264    cx.update_editor(|e, window, cx| {
 4265        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4266    });
 4267    cx.assert_editor_state(indoc! {"
 4268        «1
 4269        2
 4270        3
 4271        4
 4272        5ˇ»
 4273
 4274        «aaaaa
 4275        bb
 4276        ccc
 4277        ddddˇ»
 4278    "});
 4279
 4280    // Adding lines on each selection
 4281    cx.set_state(indoc! {"
 4282 4283        1ˇ»
 4284
 4285        bb«bb
 4286        aaaˇ»aa
 4287    "});
 4288    cx.update_editor(|e, window, cx| {
 4289        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4290    });
 4291    cx.assert_editor_state(indoc! {"
 4292        «2
 4293        1
 4294        added lineˇ»
 4295
 4296        «bbbb
 4297        aaaaa
 4298        added lineˇ»
 4299    "});
 4300
 4301    // Removing lines on each selection
 4302    cx.set_state(indoc! {"
 4303 4304        1ˇ»
 4305
 4306        bb«bb
 4307        aaaˇ»aa
 4308    "});
 4309    cx.update_editor(|e, window, cx| {
 4310        e.manipulate_immutable_lines(window, cx, |lines| {
 4311            lines.pop();
 4312        })
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «2ˇ»
 4316
 4317        «bbbbˇ»
 4318    "});
 4319}
 4320
 4321#[gpui::test]
 4322async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4323    init_test(cx, |settings| {
 4324        settings.defaults.tab_size = NonZeroU32::new(3)
 4325    });
 4326
 4327    let mut cx = EditorTestContext::new(cx).await;
 4328
 4329    // MULTI SELECTION
 4330    // Ln.1 "«" tests empty lines
 4331    // Ln.9 tests just leading whitespace
 4332    cx.set_state(indoc! {"
 4333        «
 4334        abc                 // No indentationˇ»
 4335        «\tabc              // 1 tabˇ»
 4336        \t\tabc «      ˇ»   // 2 tabs
 4337        \t ab«c             // Tab followed by space
 4338         \tabc              // Space followed by tab (3 spaces should be the result)
 4339        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4340           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4341        \t
 4342        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4343    "});
 4344    cx.update_editor(|e, window, cx| {
 4345        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4346    });
 4347    cx.assert_editor_state(
 4348        indoc! {"
 4349            «
 4350            abc                 // No indentation
 4351               abc              // 1 tab
 4352                  abc          // 2 tabs
 4353                abc             // Tab followed by space
 4354               abc              // Space followed by tab (3 spaces should be the result)
 4355                           abc   // Mixed indentation (tab conversion depends on the column)
 4356               abc         // Already space indented
 4357               ·
 4358               abc\tdef          // Only the leading tab is manipulatedˇ»
 4359        "}
 4360        .replace("·", "")
 4361        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4362    );
 4363
 4364    // Test on just a few lines, the others should remain unchanged
 4365    // Only lines (3, 5, 10, 11) should change
 4366    cx.set_state(
 4367        indoc! {"
 4368            ·
 4369            abc                 // No indentation
 4370            \tabcˇ               // 1 tab
 4371            \t\tabc             // 2 tabs
 4372            \t abcˇ              // Tab followed by space
 4373             \tabc              // Space followed by tab (3 spaces should be the result)
 4374            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4375               abc              // Already space indented
 4376            «\t
 4377            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4378        "}
 4379        .replace("·", "")
 4380        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4381    );
 4382    cx.update_editor(|e, window, cx| {
 4383        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4384    });
 4385    cx.assert_editor_state(
 4386        indoc! {"
 4387            ·
 4388            abc                 // No indentation
 4389            «   abc               // 1 tabˇ»
 4390            \t\tabc             // 2 tabs
 4391            «    abc              // Tab followed by spaceˇ»
 4392             \tabc              // Space followed by tab (3 spaces should be the result)
 4393            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4394               abc              // Already space indented
 4395            «   ·
 4396               abc\tdef          // Only the leading tab is manipulatedˇ»
 4397        "}
 4398        .replace("·", "")
 4399        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4400    );
 4401
 4402    // SINGLE SELECTION
 4403    // Ln.1 "«" tests empty lines
 4404    // Ln.9 tests just leading whitespace
 4405    cx.set_state(indoc! {"
 4406        «
 4407        abc                 // No indentation
 4408        \tabc               // 1 tab
 4409        \t\tabc             // 2 tabs
 4410        \t abc              // Tab followed by space
 4411         \tabc              // Space followed by tab (3 spaces should be the result)
 4412        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4413           abc              // Already space indented
 4414        \t
 4415        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4419    });
 4420    cx.assert_editor_state(
 4421        indoc! {"
 4422            «
 4423            abc                 // No indentation
 4424               abc               // 1 tab
 4425                  abc             // 2 tabs
 4426                abc              // Tab followed by space
 4427               abc              // Space followed by tab (3 spaces should be the result)
 4428                           abc   // Mixed indentation (tab conversion depends on the column)
 4429               abc              // Already space indented
 4430               ·
 4431               abc\tdef          // Only the leading tab is manipulatedˇ»
 4432        "}
 4433        .replace("·", "")
 4434        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4435    );
 4436}
 4437
 4438#[gpui::test]
 4439async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4440    init_test(cx, |settings| {
 4441        settings.defaults.tab_size = NonZeroU32::new(3)
 4442    });
 4443
 4444    let mut cx = EditorTestContext::new(cx).await;
 4445
 4446    // MULTI SELECTION
 4447    // Ln.1 "«" tests empty lines
 4448    // Ln.11 tests just leading whitespace
 4449    cx.set_state(indoc! {"
 4450        «
 4451        abˇ»ˇc                 // No indentation
 4452         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4453          abc  «             // 2 spaces (< 3 so dont convert)
 4454           abc              // 3 spaces (convert)
 4455             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4456        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4457        «\t abc              // Tab followed by space
 4458         \tabc              // Space followed by tab (should be consumed due to tab)
 4459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4460           \tˇ»  «\t
 4461           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4462    "});
 4463    cx.update_editor(|e, window, cx| {
 4464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4465    });
 4466    cx.assert_editor_state(indoc! {"
 4467        «
 4468        abc                 // No indentation
 4469         abc                // 1 space (< 3 so dont convert)
 4470          abc               // 2 spaces (< 3 so dont convert)
 4471        \tabc              // 3 spaces (convert)
 4472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4473        \t\t\tabc           // Already tab indented
 4474        \t abc              // Tab followed by space
 4475        \tabc              // Space followed by tab (should be consumed due to tab)
 4476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4477        \t\t\t
 4478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4479    "});
 4480
 4481    // Test on just a few lines, the other should remain unchanged
 4482    // Only lines (4, 8, 11, 12) should change
 4483    cx.set_state(
 4484        indoc! {"
 4485            ·
 4486            abc                 // No indentation
 4487             abc                // 1 space (< 3 so dont convert)
 4488              abc               // 2 spaces (< 3 so dont convert)
 4489            «   abc              // 3 spaces (convert)ˇ»
 4490                 abc            // 5 spaces (1 tab + 2 spaces)
 4491            \t\t\tabc           // Already tab indented
 4492            \t abc              // Tab followed by space
 4493             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4494               \t\t  \tabc      // Mixed indentation
 4495            \t \t  \t   \tabc   // Mixed indentation
 4496               \t  \tˇ
 4497            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4498        "}
 4499        .replace("·", "")
 4500        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4501    );
 4502    cx.update_editor(|e, window, cx| {
 4503        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4504    });
 4505    cx.assert_editor_state(
 4506        indoc! {"
 4507            ·
 4508            abc                 // No indentation
 4509             abc                // 1 space (< 3 so dont convert)
 4510              abc               // 2 spaces (< 3 so dont convert)
 4511            «\tabc              // 3 spaces (convert)ˇ»
 4512                 abc            // 5 spaces (1 tab + 2 spaces)
 4513            \t\t\tabc           // Already tab indented
 4514            \t abc              // Tab followed by space
 4515            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4516               \t\t  \tabc      // Mixed indentation
 4517            \t \t  \t   \tabc   // Mixed indentation
 4518            «\t\t\t
 4519            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4520        "}
 4521        .replace("·", "")
 4522        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4523    );
 4524
 4525    // SINGLE SELECTION
 4526    // Ln.1 "«" tests empty lines
 4527    // Ln.11 tests just leading whitespace
 4528    cx.set_state(indoc! {"
 4529        «
 4530        abc                 // No indentation
 4531         abc                // 1 space (< 3 so dont convert)
 4532          abc               // 2 spaces (< 3 so dont convert)
 4533           abc              // 3 spaces (convert)
 4534             abc            // 5 spaces (1 tab + 2 spaces)
 4535        \t\t\tabc           // Already tab indented
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (should be consumed due to tab)
 4538        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4539           \t  \t
 4540           abc   \t         // Only the leading spaces should be convertedˇ»
 4541    "});
 4542    cx.update_editor(|e, window, cx| {
 4543        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4544    });
 4545    cx.assert_editor_state(indoc! {"
 4546        «
 4547        abc                 // No indentation
 4548         abc                // 1 space (< 3 so dont convert)
 4549          abc               // 2 spaces (< 3 so dont convert)
 4550        \tabc              // 3 spaces (convert)
 4551        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4552        \t\t\tabc           // Already tab indented
 4553        \t abc              // Tab followed by space
 4554        \tabc              // Space followed by tab (should be consumed due to tab)
 4555        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4556        \t\t\t
 4557        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4558    "});
 4559}
 4560
 4561#[gpui::test]
 4562async fn test_toggle_case(cx: &mut TestAppContext) {
 4563    init_test(cx, |_| {});
 4564
 4565    let mut cx = EditorTestContext::new(cx).await;
 4566
 4567    // If all lower case -> upper case
 4568    cx.set_state(indoc! {"
 4569        «hello worldˇ»
 4570    "});
 4571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4572    cx.assert_editor_state(indoc! {"
 4573        «HELLO WORLDˇ»
 4574    "});
 4575
 4576    // If all upper case -> lower case
 4577    cx.set_state(indoc! {"
 4578        «HELLO WORLDˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4581    cx.assert_editor_state(indoc! {"
 4582        «hello worldˇ»
 4583    "});
 4584
 4585    // If any upper case characters are identified -> lower case
 4586    // This matches JetBrains IDEs
 4587    cx.set_state(indoc! {"
 4588        «hEllo worldˇ»
 4589    "});
 4590    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4591    cx.assert_editor_state(indoc! {"
 4592        «hello worldˇ»
 4593    "});
 4594}
 4595
 4596#[gpui::test]
 4597async fn test_manipulate_text(cx: &mut TestAppContext) {
 4598    init_test(cx, |_| {});
 4599
 4600    let mut cx = EditorTestContext::new(cx).await;
 4601
 4602    // Test convert_to_upper_case()
 4603    cx.set_state(indoc! {"
 4604        «hello worldˇ»
 4605    "});
 4606    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4607    cx.assert_editor_state(indoc! {"
 4608        «HELLO WORLDˇ»
 4609    "});
 4610
 4611    // Test convert_to_lower_case()
 4612    cx.set_state(indoc! {"
 4613        «HELLO WORLDˇ»
 4614    "});
 4615    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4616    cx.assert_editor_state(indoc! {"
 4617        «hello worldˇ»
 4618    "});
 4619
 4620    // Test multiple line, single selection case
 4621    cx.set_state(indoc! {"
 4622        «The quick brown
 4623        fox jumps over
 4624        the lazy dogˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4627    cx.assert_editor_state(indoc! {"
 4628        «The Quick Brown
 4629        Fox Jumps Over
 4630        The Lazy Dogˇ»
 4631    "});
 4632
 4633    // Test multiple line, single selection case
 4634    cx.set_state(indoc! {"
 4635        «The quick brown
 4636        fox jumps over
 4637        the lazy dogˇ»
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «TheQuickBrown
 4644        FoxJumpsOver
 4645        TheLazyDogˇ»
 4646    "});
 4647
 4648    // From here on out, test more complex cases of manipulate_text()
 4649
 4650    // Test no selection case - should affect words cursors are in
 4651    // Cursor at beginning, middle, and end of word
 4652    cx.set_state(indoc! {"
 4653        ˇhello big beauˇtiful worldˇ
 4654    "});
 4655    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4656    cx.assert_editor_state(indoc! {"
 4657        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4658    "});
 4659
 4660    // Test multiple selections on a single line and across multiple lines
 4661    cx.set_state(indoc! {"
 4662        «Theˇ» quick «brown
 4663        foxˇ» jumps «overˇ»
 4664        the «lazyˇ» dog
 4665    "});
 4666    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4667    cx.assert_editor_state(indoc! {"
 4668        «THEˇ» quick «BROWN
 4669        FOXˇ» jumps «OVERˇ»
 4670        the «LAZYˇ» dog
 4671    "});
 4672
 4673    // Test case where text length grows
 4674    cx.set_state(indoc! {"
 4675        «tschüߡ»
 4676    "});
 4677    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4678    cx.assert_editor_state(indoc! {"
 4679        «TSCHÜSSˇ»
 4680    "});
 4681
 4682    // Test to make sure we don't crash when text shrinks
 4683    cx.set_state(indoc! {"
 4684        aaa_bbbˇ
 4685    "});
 4686    cx.update_editor(|e, window, cx| {
 4687        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4688    });
 4689    cx.assert_editor_state(indoc! {"
 4690        «aaaBbbˇ»
 4691    "});
 4692
 4693    // Test to make sure we all aware of the fact that each word can grow and shrink
 4694    // Final selections should be aware of this fact
 4695    cx.set_state(indoc! {"
 4696        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4697    "});
 4698    cx.update_editor(|e, window, cx| {
 4699        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4700    });
 4701    cx.assert_editor_state(indoc! {"
 4702        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4703    "});
 4704
 4705    cx.set_state(indoc! {"
 4706        «hElLo, WoRld!ˇ»
 4707    "});
 4708    cx.update_editor(|e, window, cx| {
 4709        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4710    });
 4711    cx.assert_editor_state(indoc! {"
 4712        «HeLlO, wOrLD!ˇ»
 4713    "});
 4714}
 4715
 4716#[gpui::test]
 4717fn test_duplicate_line(cx: &mut TestAppContext) {
 4718    init_test(cx, |_| {});
 4719
 4720    let editor = cx.add_window(|window, cx| {
 4721        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4722        build_editor(buffer, window, cx)
 4723    });
 4724    _ = editor.update(cx, |editor, window, cx| {
 4725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4726            s.select_display_ranges([
 4727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4728                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4729                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4730                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4731            ])
 4732        });
 4733        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4734        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4735        assert_eq!(
 4736            editor.selections.display_ranges(cx),
 4737            vec![
 4738                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4740                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4741                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4742            ]
 4743        );
 4744    });
 4745
 4746    let editor = cx.add_window(|window, cx| {
 4747        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4748        build_editor(buffer, window, cx)
 4749    });
 4750    _ = editor.update(cx, |editor, window, cx| {
 4751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4752            s.select_display_ranges([
 4753                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4754                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4755            ])
 4756        });
 4757        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4758        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4759        assert_eq!(
 4760            editor.selections.display_ranges(cx),
 4761            vec![
 4762                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4763                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4764            ]
 4765        );
 4766    });
 4767
 4768    // With `move_upwards` the selections stay in place, except for
 4769    // the lines inserted above them
 4770    let editor = cx.add_window(|window, cx| {
 4771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4772        build_editor(buffer, window, cx)
 4773    });
 4774    _ = editor.update(cx, |editor, window, cx| {
 4775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4776            s.select_display_ranges([
 4777                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4778                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4779                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4780                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4781            ])
 4782        });
 4783        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4784        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4785        assert_eq!(
 4786            editor.selections.display_ranges(cx),
 4787            vec![
 4788                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4789                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4790                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4791                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4792            ]
 4793        );
 4794    });
 4795
 4796    let editor = cx.add_window(|window, cx| {
 4797        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4798        build_editor(buffer, window, cx)
 4799    });
 4800    _ = editor.update(cx, |editor, window, cx| {
 4801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4802            s.select_display_ranges([
 4803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4804                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4805            ])
 4806        });
 4807        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4808        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4809        assert_eq!(
 4810            editor.selections.display_ranges(cx),
 4811            vec![
 4812                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4813                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4814            ]
 4815        );
 4816    });
 4817
 4818    let editor = cx.add_window(|window, cx| {
 4819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4820        build_editor(buffer, window, cx)
 4821    });
 4822    _ = editor.update(cx, |editor, window, cx| {
 4823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4824            s.select_display_ranges([
 4825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4827            ])
 4828        });
 4829        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4830        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4831        assert_eq!(
 4832            editor.selections.display_ranges(cx),
 4833            vec![
 4834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4836            ]
 4837        );
 4838    });
 4839}
 4840
 4841#[gpui::test]
 4842fn test_move_line_up_down(cx: &mut TestAppContext) {
 4843    init_test(cx, |_| {});
 4844
 4845    let editor = cx.add_window(|window, cx| {
 4846        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4847        build_editor(buffer, window, cx)
 4848    });
 4849    _ = editor.update(cx, |editor, window, cx| {
 4850        editor.fold_creases(
 4851            vec![
 4852                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4853                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4854                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4855            ],
 4856            true,
 4857            window,
 4858            cx,
 4859        );
 4860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4861            s.select_display_ranges([
 4862                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4863                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4864                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4865                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4866            ])
 4867        });
 4868        assert_eq!(
 4869            editor.display_text(cx),
 4870            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4871        );
 4872
 4873        editor.move_line_up(&MoveLineUp, window, cx);
 4874        assert_eq!(
 4875            editor.display_text(cx),
 4876            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4877        );
 4878        assert_eq!(
 4879            editor.selections.display_ranges(cx),
 4880            vec![
 4881                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4882                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4883                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4884                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4885            ]
 4886        );
 4887    });
 4888
 4889    _ = editor.update(cx, |editor, window, cx| {
 4890        editor.move_line_down(&MoveLineDown, window, cx);
 4891        assert_eq!(
 4892            editor.display_text(cx),
 4893            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4894        );
 4895        assert_eq!(
 4896            editor.selections.display_ranges(cx),
 4897            vec![
 4898                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4899                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4900                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4901                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4902            ]
 4903        );
 4904    });
 4905
 4906    _ = editor.update(cx, |editor, window, cx| {
 4907        editor.move_line_down(&MoveLineDown, window, cx);
 4908        assert_eq!(
 4909            editor.display_text(cx),
 4910            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4911        );
 4912        assert_eq!(
 4913            editor.selections.display_ranges(cx),
 4914            vec![
 4915                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4916                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4917                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4918                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4919            ]
 4920        );
 4921    });
 4922
 4923    _ = editor.update(cx, |editor, window, cx| {
 4924        editor.move_line_up(&MoveLineUp, window, cx);
 4925        assert_eq!(
 4926            editor.display_text(cx),
 4927            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4928        );
 4929        assert_eq!(
 4930            editor.selections.display_ranges(cx),
 4931            vec![
 4932                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4933                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4934                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4936            ]
 4937        );
 4938    });
 4939}
 4940
 4941#[gpui::test]
 4942fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4943    init_test(cx, |_| {});
 4944
 4945    let editor = cx.add_window(|window, cx| {
 4946        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4947        build_editor(buffer, window, cx)
 4948    });
 4949    _ = editor.update(cx, |editor, window, cx| {
 4950        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4951        editor.insert_blocks(
 4952            [BlockProperties {
 4953                style: BlockStyle::Fixed,
 4954                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4955                height: Some(1),
 4956                render: Arc::new(|_| div().into_any()),
 4957                priority: 0,
 4958                render_in_minimap: true,
 4959            }],
 4960            Some(Autoscroll::fit()),
 4961            cx,
 4962        );
 4963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4964            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4965        });
 4966        editor.move_line_down(&MoveLineDown, window, cx);
 4967    });
 4968}
 4969
 4970#[gpui::test]
 4971async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4972    init_test(cx, |_| {});
 4973
 4974    let mut cx = EditorTestContext::new(cx).await;
 4975    cx.set_state(
 4976        &"
 4977            ˇzero
 4978            one
 4979            two
 4980            three
 4981            four
 4982            five
 4983        "
 4984        .unindent(),
 4985    );
 4986
 4987    // Create a four-line block that replaces three lines of text.
 4988    cx.update_editor(|editor, window, cx| {
 4989        let snapshot = editor.snapshot(window, cx);
 4990        let snapshot = &snapshot.buffer_snapshot;
 4991        let placement = BlockPlacement::Replace(
 4992            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4993        );
 4994        editor.insert_blocks(
 4995            [BlockProperties {
 4996                placement,
 4997                height: Some(4),
 4998                style: BlockStyle::Sticky,
 4999                render: Arc::new(|_| gpui::div().into_any_element()),
 5000                priority: 0,
 5001                render_in_minimap: true,
 5002            }],
 5003            None,
 5004            cx,
 5005        );
 5006    });
 5007
 5008    // Move down so that the cursor touches the block.
 5009    cx.update_editor(|editor, window, cx| {
 5010        editor.move_down(&Default::default(), window, cx);
 5011    });
 5012    cx.assert_editor_state(
 5013        &"
 5014            zero
 5015            «one
 5016            two
 5017            threeˇ»
 5018            four
 5019            five
 5020        "
 5021        .unindent(),
 5022    );
 5023
 5024    // Move down past the block.
 5025    cx.update_editor(|editor, window, cx| {
 5026        editor.move_down(&Default::default(), window, cx);
 5027    });
 5028    cx.assert_editor_state(
 5029        &"
 5030            zero
 5031            one
 5032            two
 5033            three
 5034            ˇfour
 5035            five
 5036        "
 5037        .unindent(),
 5038    );
 5039}
 5040
 5041#[gpui::test]
 5042fn test_transpose(cx: &mut TestAppContext) {
 5043    init_test(cx, |_| {});
 5044
 5045    _ = cx.add_window(|window, cx| {
 5046        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5047        editor.set_style(EditorStyle::default(), window, cx);
 5048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5049            s.select_ranges([1..1])
 5050        });
 5051        editor.transpose(&Default::default(), window, cx);
 5052        assert_eq!(editor.text(cx), "bac");
 5053        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5054
 5055        editor.transpose(&Default::default(), window, cx);
 5056        assert_eq!(editor.text(cx), "bca");
 5057        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5058
 5059        editor.transpose(&Default::default(), window, cx);
 5060        assert_eq!(editor.text(cx), "bac");
 5061        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5062
 5063        editor
 5064    });
 5065
 5066    _ = cx.add_window(|window, cx| {
 5067        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5068        editor.set_style(EditorStyle::default(), window, cx);
 5069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5070            s.select_ranges([3..3])
 5071        });
 5072        editor.transpose(&Default::default(), window, cx);
 5073        assert_eq!(editor.text(cx), "acb\nde");
 5074        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5075
 5076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5077            s.select_ranges([4..4])
 5078        });
 5079        editor.transpose(&Default::default(), window, cx);
 5080        assert_eq!(editor.text(cx), "acbd\ne");
 5081        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5082
 5083        editor.transpose(&Default::default(), window, cx);
 5084        assert_eq!(editor.text(cx), "acbde\n");
 5085        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5086
 5087        editor.transpose(&Default::default(), window, cx);
 5088        assert_eq!(editor.text(cx), "acbd\ne");
 5089        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5090
 5091        editor
 5092    });
 5093
 5094    _ = cx.add_window(|window, cx| {
 5095        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5096        editor.set_style(EditorStyle::default(), window, cx);
 5097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5098            s.select_ranges([1..1, 2..2, 4..4])
 5099        });
 5100        editor.transpose(&Default::default(), window, cx);
 5101        assert_eq!(editor.text(cx), "bacd\ne");
 5102        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5103
 5104        editor.transpose(&Default::default(), window, cx);
 5105        assert_eq!(editor.text(cx), "bcade\n");
 5106        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5107
 5108        editor.transpose(&Default::default(), window, cx);
 5109        assert_eq!(editor.text(cx), "bcda\ne");
 5110        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5111
 5112        editor.transpose(&Default::default(), window, cx);
 5113        assert_eq!(editor.text(cx), "bcade\n");
 5114        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5115
 5116        editor.transpose(&Default::default(), window, cx);
 5117        assert_eq!(editor.text(cx), "bcaed\n");
 5118        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5119
 5120        editor
 5121    });
 5122
 5123    _ = cx.add_window(|window, cx| {
 5124        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5125        editor.set_style(EditorStyle::default(), window, cx);
 5126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5127            s.select_ranges([4..4])
 5128        });
 5129        editor.transpose(&Default::default(), window, cx);
 5130        assert_eq!(editor.text(cx), "🏀🍐✋");
 5131        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5132
 5133        editor.transpose(&Default::default(), window, cx);
 5134        assert_eq!(editor.text(cx), "🏀✋🍐");
 5135        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5136
 5137        editor.transpose(&Default::default(), window, cx);
 5138        assert_eq!(editor.text(cx), "🏀🍐✋");
 5139        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5140
 5141        editor
 5142    });
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_rewrap(cx: &mut TestAppContext) {
 5147    init_test(cx, |settings| {
 5148        settings.languages.extend([
 5149            (
 5150                "Markdown".into(),
 5151                LanguageSettingsContent {
 5152                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5153                    preferred_line_length: Some(40),
 5154                    ..Default::default()
 5155                },
 5156            ),
 5157            (
 5158                "Plain Text".into(),
 5159                LanguageSettingsContent {
 5160                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5161                    preferred_line_length: Some(40),
 5162                    ..Default::default()
 5163                },
 5164            ),
 5165            (
 5166                "C++".into(),
 5167                LanguageSettingsContent {
 5168                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5169                    preferred_line_length: Some(40),
 5170                    ..Default::default()
 5171                },
 5172            ),
 5173            (
 5174                "Python".into(),
 5175                LanguageSettingsContent {
 5176                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5177                    preferred_line_length: Some(40),
 5178                    ..Default::default()
 5179                },
 5180            ),
 5181            (
 5182                "Rust".into(),
 5183                LanguageSettingsContent {
 5184                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5185                    preferred_line_length: Some(40),
 5186                    ..Default::default()
 5187                },
 5188            ),
 5189        ])
 5190    });
 5191
 5192    let mut cx = EditorTestContext::new(cx).await;
 5193
 5194    let cpp_language = Arc::new(Language::new(
 5195        LanguageConfig {
 5196            name: "C++".into(),
 5197            line_comments: vec!["// ".into()],
 5198            ..LanguageConfig::default()
 5199        },
 5200        None,
 5201    ));
 5202    let python_language = Arc::new(Language::new(
 5203        LanguageConfig {
 5204            name: "Python".into(),
 5205            line_comments: vec!["# ".into()],
 5206            ..LanguageConfig::default()
 5207        },
 5208        None,
 5209    ));
 5210    let markdown_language = Arc::new(Language::new(
 5211        LanguageConfig {
 5212            name: "Markdown".into(),
 5213            ..LanguageConfig::default()
 5214        },
 5215        None,
 5216    ));
 5217    let rust_language = Arc::new(Language::new(
 5218        LanguageConfig {
 5219            name: "Rust".into(),
 5220            line_comments: vec!["// ".into(), "/// ".into()],
 5221            ..LanguageConfig::default()
 5222        },
 5223        Some(tree_sitter_rust::LANGUAGE.into()),
 5224    ));
 5225
 5226    let plaintext_language = Arc::new(Language::new(
 5227        LanguageConfig {
 5228            name: "Plain Text".into(),
 5229            ..LanguageConfig::default()
 5230        },
 5231        None,
 5232    ));
 5233
 5234    // Test basic rewrapping of a long line with a cursor
 5235    assert_rewrap(
 5236        indoc! {"
 5237            // ˇThis is a long comment that needs to be wrapped.
 5238        "},
 5239        indoc! {"
 5240            // ˇThis is a long comment that needs to
 5241            // be wrapped.
 5242        "},
 5243        cpp_language.clone(),
 5244        &mut cx,
 5245    );
 5246
 5247    // Test rewrapping a full selection
 5248    assert_rewrap(
 5249        indoc! {"
 5250            «// This selected long comment needs to be wrapped.ˇ»"
 5251        },
 5252        indoc! {"
 5253            «// This selected long comment needs to
 5254            // be wrapped.ˇ»"
 5255        },
 5256        cpp_language.clone(),
 5257        &mut cx,
 5258    );
 5259
 5260    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5261    assert_rewrap(
 5262        indoc! {"
 5263            // ˇThis is the first line.
 5264            // Thisˇ is the second line.
 5265            // This is the thirdˇ line, all part of one paragraph.
 5266         "},
 5267        indoc! {"
 5268            // ˇThis is the first line. Thisˇ is the
 5269            // second line. This is the thirdˇ line,
 5270            // all part of one paragraph.
 5271         "},
 5272        cpp_language.clone(),
 5273        &mut cx,
 5274    );
 5275
 5276    // Test multiple cursors in different paragraphs trigger separate rewraps
 5277    assert_rewrap(
 5278        indoc! {"
 5279            // ˇThis is the first paragraph, first line.
 5280            // ˇThis is the first paragraph, second line.
 5281
 5282            // ˇThis is the second paragraph, first line.
 5283            // ˇThis is the second paragraph, second line.
 5284        "},
 5285        indoc! {"
 5286            // ˇThis is the first paragraph, first
 5287            // line. ˇThis is the first paragraph,
 5288            // second line.
 5289
 5290            // ˇThis is the second paragraph, first
 5291            // line. ˇThis is the second paragraph,
 5292            // second line.
 5293        "},
 5294        cpp_language.clone(),
 5295        &mut cx,
 5296    );
 5297
 5298    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5299    assert_rewrap(
 5300        indoc! {"
 5301            «// A regular long long comment to be wrapped.
 5302            /// A documentation long comment to be wrapped.ˇ»
 5303          "},
 5304        indoc! {"
 5305            «// A regular long long comment to be
 5306            // wrapped.
 5307            /// A documentation long comment to be
 5308            /// wrapped.ˇ»
 5309          "},
 5310        rust_language.clone(),
 5311        &mut cx,
 5312    );
 5313
 5314    // Test that change in indentation level trigger seperate rewraps
 5315    assert_rewrap(
 5316        indoc! {"
 5317            fn foo() {
 5318                «// This is a long comment at the base indent.
 5319                    // This is a long comment at the next indent.ˇ»
 5320            }
 5321        "},
 5322        indoc! {"
 5323            fn foo() {
 5324                «// This is a long comment at the
 5325                // base indent.
 5326                    // This is a long comment at the
 5327                    // next indent.ˇ»
 5328            }
 5329        "},
 5330        rust_language.clone(),
 5331        &mut cx,
 5332    );
 5333
 5334    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5335    assert_rewrap(
 5336        indoc! {"
 5337            # ˇThis is a long comment using a pound sign.
 5338        "},
 5339        indoc! {"
 5340            # ˇThis is a long comment using a pound
 5341            # sign.
 5342        "},
 5343        python_language.clone(),
 5344        &mut cx,
 5345    );
 5346
 5347    // Test rewrapping only affects comments, not code even when selected
 5348    assert_rewrap(
 5349        indoc! {"
 5350            «/// This doc comment is long and should be wrapped.
 5351            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5352        "},
 5353        indoc! {"
 5354            «/// This doc comment is long and should
 5355            /// be wrapped.
 5356            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5357        "},
 5358        rust_language.clone(),
 5359        &mut cx,
 5360    );
 5361
 5362    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5363    assert_rewrap(
 5364        indoc! {"
 5365            # Header
 5366
 5367            A long long long line of markdown text to wrap.ˇ
 5368         "},
 5369        indoc! {"
 5370            # Header
 5371
 5372            A long long long line of markdown text
 5373            to wrap.ˇ
 5374         "},
 5375        markdown_language,
 5376        &mut cx,
 5377    );
 5378
 5379    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5380    assert_rewrap(
 5381        indoc! {"
 5382            ˇThis is a very long line of plain text that will be wrapped.
 5383        "},
 5384        indoc! {"
 5385            ˇThis is a very long line of plain text
 5386            that will be wrapped.
 5387        "},
 5388        plaintext_language.clone(),
 5389        &mut cx,
 5390    );
 5391
 5392    // Test that non-commented code acts as a paragraph boundary within a selection
 5393    assert_rewrap(
 5394        indoc! {"
 5395               «// This is the first long comment block to be wrapped.
 5396               fn my_func(a: u32);
 5397               // This is the second long comment block to be wrapped.ˇ»
 5398           "},
 5399        indoc! {"
 5400               «// This is the first long comment block
 5401               // to be wrapped.
 5402               fn my_func(a: u32);
 5403               // This is the second long comment block
 5404               // to be wrapped.ˇ»
 5405           "},
 5406        rust_language.clone(),
 5407        &mut cx,
 5408    );
 5409
 5410    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5411    assert_rewrap(
 5412        indoc! {"
 5413            «ˇThis is a very long line that will be wrapped.
 5414
 5415            This is another paragraph in the same selection.»
 5416
 5417            «\tThis is a very long indented line that will be wrapped.ˇ»
 5418         "},
 5419        indoc! {"
 5420            «ˇThis is a very long line that will be
 5421            wrapped.
 5422
 5423            This is another paragraph in the same
 5424            selection.»
 5425
 5426            «\tThis is a very long indented line
 5427            \tthat will be wrapped.ˇ»
 5428         "},
 5429        plaintext_language.clone(),
 5430        &mut cx,
 5431    );
 5432
 5433    // Test that an empty comment line acts as a paragraph boundary
 5434    assert_rewrap(
 5435        indoc! {"
 5436            // ˇThis is a long comment that will be wrapped.
 5437            //
 5438            // And this is another long comment that will also be wrapped.ˇ
 5439         "},
 5440        indoc! {"
 5441            // ˇThis is a long comment that will be
 5442            // wrapped.
 5443            //
 5444            // And this is another long comment that
 5445            // will also be wrapped.ˇ
 5446         "},
 5447        cpp_language,
 5448        &mut cx,
 5449    );
 5450
 5451    #[track_caller]
 5452    fn assert_rewrap(
 5453        unwrapped_text: &str,
 5454        wrapped_text: &str,
 5455        language: Arc<Language>,
 5456        cx: &mut EditorTestContext,
 5457    ) {
 5458        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5459        cx.set_state(unwrapped_text);
 5460        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5461        cx.assert_editor_state(wrapped_text);
 5462    }
 5463}
 5464
 5465#[gpui::test]
 5466async fn test_hard_wrap(cx: &mut TestAppContext) {
 5467    init_test(cx, |_| {});
 5468    let mut cx = EditorTestContext::new(cx).await;
 5469
 5470    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5471    cx.update_editor(|editor, _, cx| {
 5472        editor.set_hard_wrap(Some(14), cx);
 5473    });
 5474
 5475    cx.set_state(indoc!(
 5476        "
 5477        one two three ˇ
 5478        "
 5479    ));
 5480    cx.simulate_input("four");
 5481    cx.run_until_parked();
 5482
 5483    cx.assert_editor_state(indoc!(
 5484        "
 5485        one two three
 5486        fourˇ
 5487        "
 5488    ));
 5489
 5490    cx.update_editor(|editor, window, cx| {
 5491        editor.newline(&Default::default(), window, cx);
 5492    });
 5493    cx.run_until_parked();
 5494    cx.assert_editor_state(indoc!(
 5495        "
 5496        one two three
 5497        four
 5498        ˇ
 5499        "
 5500    ));
 5501
 5502    cx.simulate_input("five");
 5503    cx.run_until_parked();
 5504    cx.assert_editor_state(indoc!(
 5505        "
 5506        one two three
 5507        four
 5508        fiveˇ
 5509        "
 5510    ));
 5511
 5512    cx.update_editor(|editor, window, cx| {
 5513        editor.newline(&Default::default(), window, cx);
 5514    });
 5515    cx.run_until_parked();
 5516    cx.simulate_input("# ");
 5517    cx.run_until_parked();
 5518    cx.assert_editor_state(indoc!(
 5519        "
 5520        one two three
 5521        four
 5522        five
 5523        # ˇ
 5524        "
 5525    ));
 5526
 5527    cx.update_editor(|editor, window, cx| {
 5528        editor.newline(&Default::default(), window, cx);
 5529    });
 5530    cx.run_until_parked();
 5531    cx.assert_editor_state(indoc!(
 5532        "
 5533        one two three
 5534        four
 5535        five
 5536        #\x20
 5537 5538        "
 5539    ));
 5540
 5541    cx.simulate_input(" 6");
 5542    cx.run_until_parked();
 5543    cx.assert_editor_state(indoc!(
 5544        "
 5545        one two three
 5546        four
 5547        five
 5548        #
 5549        # 6ˇ
 5550        "
 5551    ));
 5552}
 5553
 5554#[gpui::test]
 5555async fn test_clipboard(cx: &mut TestAppContext) {
 5556    init_test(cx, |_| {});
 5557
 5558    let mut cx = EditorTestContext::new(cx).await;
 5559
 5560    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5561    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5562    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5563
 5564    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5565    cx.set_state("two ˇfour ˇsix ˇ");
 5566    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5567    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5568
 5569    // Paste again but with only two cursors. Since the number of cursors doesn't
 5570    // match the number of slices in the clipboard, the entire clipboard text
 5571    // is pasted at each cursor.
 5572    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5573    cx.update_editor(|e, window, cx| {
 5574        e.handle_input("( ", window, cx);
 5575        e.paste(&Paste, window, cx);
 5576        e.handle_input(") ", window, cx);
 5577    });
 5578    cx.assert_editor_state(
 5579        &([
 5580            "( one✅ ",
 5581            "three ",
 5582            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5583            "three ",
 5584            "five ) ˇ",
 5585        ]
 5586        .join("\n")),
 5587    );
 5588
 5589    // Cut with three selections, one of which is full-line.
 5590    cx.set_state(indoc! {"
 5591        1«2ˇ»3
 5592        4ˇ567
 5593        «8ˇ»9"});
 5594    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5595    cx.assert_editor_state(indoc! {"
 5596        1ˇ3
 5597        ˇ9"});
 5598
 5599    // Paste with three selections, noticing how the copied selection that was full-line
 5600    // gets inserted before the second cursor.
 5601    cx.set_state(indoc! {"
 5602        1ˇ3
 5603 5604        «oˇ»ne"});
 5605    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5606    cx.assert_editor_state(indoc! {"
 5607        12ˇ3
 5608        4567
 5609 5610        8ˇne"});
 5611
 5612    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5613    cx.set_state(indoc! {"
 5614        The quick brown
 5615        fox juˇmps over
 5616        the lazy dog"});
 5617    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5618    assert_eq!(
 5619        cx.read_from_clipboard()
 5620            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5621        Some("fox jumps over\n".to_string())
 5622    );
 5623
 5624    // Paste with three selections, noticing how the copied full-line selection is inserted
 5625    // before the empty selections but replaces the selection that is non-empty.
 5626    cx.set_state(indoc! {"
 5627        Tˇhe quick brown
 5628        «foˇ»x jumps over
 5629        tˇhe lazy dog"});
 5630    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5631    cx.assert_editor_state(indoc! {"
 5632        fox jumps over
 5633        Tˇhe quick brown
 5634        fox jumps over
 5635        ˇx jumps over
 5636        fox jumps over
 5637        tˇhe lazy dog"});
 5638}
 5639
 5640#[gpui::test]
 5641async fn test_copy_trim(cx: &mut TestAppContext) {
 5642    init_test(cx, |_| {});
 5643
 5644    let mut cx = EditorTestContext::new(cx).await;
 5645    cx.set_state(
 5646        r#"            «for selection in selections.iter() {
 5647            let mut start = selection.start;
 5648            let mut end = selection.end;
 5649            let is_entire_line = selection.is_empty();
 5650            if is_entire_line {
 5651                start = Point::new(start.row, 0);ˇ»
 5652                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5653            }
 5654        "#,
 5655    );
 5656    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5657    assert_eq!(
 5658        cx.read_from_clipboard()
 5659            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5660        Some(
 5661            "for selection in selections.iter() {
 5662            let mut start = selection.start;
 5663            let mut end = selection.end;
 5664            let is_entire_line = selection.is_empty();
 5665            if is_entire_line {
 5666                start = Point::new(start.row, 0);"
 5667                .to_string()
 5668        ),
 5669        "Regular copying preserves all indentation selected",
 5670    );
 5671    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5672    assert_eq!(
 5673        cx.read_from_clipboard()
 5674            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5675        Some(
 5676            "for selection in selections.iter() {
 5677let mut start = selection.start;
 5678let mut end = selection.end;
 5679let is_entire_line = selection.is_empty();
 5680if is_entire_line {
 5681    start = Point::new(start.row, 0);"
 5682                .to_string()
 5683        ),
 5684        "Copying with stripping should strip all leading whitespaces"
 5685    );
 5686
 5687    cx.set_state(
 5688        r#"       «     for selection in selections.iter() {
 5689            let mut start = selection.start;
 5690            let mut end = selection.end;
 5691            let is_entire_line = selection.is_empty();
 5692            if is_entire_line {
 5693                start = Point::new(start.row, 0);ˇ»
 5694                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5695            }
 5696        "#,
 5697    );
 5698    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5699    assert_eq!(
 5700        cx.read_from_clipboard()
 5701            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5702        Some(
 5703            "     for selection in selections.iter() {
 5704            let mut start = selection.start;
 5705            let mut end = selection.end;
 5706            let is_entire_line = selection.is_empty();
 5707            if is_entire_line {
 5708                start = Point::new(start.row, 0);"
 5709                .to_string()
 5710        ),
 5711        "Regular copying preserves all indentation selected",
 5712    );
 5713    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5714    assert_eq!(
 5715        cx.read_from_clipboard()
 5716            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5717        Some(
 5718            "for selection in selections.iter() {
 5719let mut start = selection.start;
 5720let mut end = selection.end;
 5721let is_entire_line = selection.is_empty();
 5722if is_entire_line {
 5723    start = Point::new(start.row, 0);"
 5724                .to_string()
 5725        ),
 5726        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5727    );
 5728
 5729    cx.set_state(
 5730        r#"       «ˇ     for selection in selections.iter() {
 5731            let mut start = selection.start;
 5732            let mut end = selection.end;
 5733            let is_entire_line = selection.is_empty();
 5734            if is_entire_line {
 5735                start = Point::new(start.row, 0);»
 5736                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5737            }
 5738        "#,
 5739    );
 5740    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5741    assert_eq!(
 5742        cx.read_from_clipboard()
 5743            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5744        Some(
 5745            "     for selection in selections.iter() {
 5746            let mut start = selection.start;
 5747            let mut end = selection.end;
 5748            let is_entire_line = selection.is_empty();
 5749            if is_entire_line {
 5750                start = Point::new(start.row, 0);"
 5751                .to_string()
 5752        ),
 5753        "Regular copying for reverse selection works the same",
 5754    );
 5755    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5756    assert_eq!(
 5757        cx.read_from_clipboard()
 5758            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5759        Some(
 5760            "for selection in selections.iter() {
 5761let mut start = selection.start;
 5762let mut end = selection.end;
 5763let is_entire_line = selection.is_empty();
 5764if is_entire_line {
 5765    start = Point::new(start.row, 0);"
 5766                .to_string()
 5767        ),
 5768        "Copying with stripping for reverse selection works the same"
 5769    );
 5770
 5771    cx.set_state(
 5772        r#"            for selection «in selections.iter() {
 5773            let mut start = selection.start;
 5774            let mut end = selection.end;
 5775            let is_entire_line = selection.is_empty();
 5776            if is_entire_line {
 5777                start = Point::new(start.row, 0);ˇ»
 5778                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5779            }
 5780        "#,
 5781    );
 5782    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5783    assert_eq!(
 5784        cx.read_from_clipboard()
 5785            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5786        Some(
 5787            "in selections.iter() {
 5788            let mut start = selection.start;
 5789            let mut end = selection.end;
 5790            let is_entire_line = selection.is_empty();
 5791            if is_entire_line {
 5792                start = Point::new(start.row, 0);"
 5793                .to_string()
 5794        ),
 5795        "When selecting past the indent, the copying works as usual",
 5796    );
 5797    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5798    assert_eq!(
 5799        cx.read_from_clipboard()
 5800            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5801        Some(
 5802            "in selections.iter() {
 5803            let mut start = selection.start;
 5804            let mut end = selection.end;
 5805            let is_entire_line = selection.is_empty();
 5806            if is_entire_line {
 5807                start = Point::new(start.row, 0);"
 5808                .to_string()
 5809        ),
 5810        "When selecting past the indent, nothing is trimmed"
 5811    );
 5812
 5813    cx.set_state(
 5814        r#"            «for selection in selections.iter() {
 5815            let mut start = selection.start;
 5816
 5817            let mut end = selection.end;
 5818            let is_entire_line = selection.is_empty();
 5819            if is_entire_line {
 5820                start = Point::new(start.row, 0);
 5821ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5822            }
 5823        "#,
 5824    );
 5825    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5826    assert_eq!(
 5827        cx.read_from_clipboard()
 5828            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5829        Some(
 5830            "for selection in selections.iter() {
 5831let mut start = selection.start;
 5832
 5833let mut end = selection.end;
 5834let is_entire_line = selection.is_empty();
 5835if is_entire_line {
 5836    start = Point::new(start.row, 0);
 5837"
 5838            .to_string()
 5839        ),
 5840        "Copying with stripping should ignore empty lines"
 5841    );
 5842}
 5843
 5844#[gpui::test]
 5845async fn test_paste_multiline(cx: &mut TestAppContext) {
 5846    init_test(cx, |_| {});
 5847
 5848    let mut cx = EditorTestContext::new(cx).await;
 5849    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5850
 5851    // Cut an indented block, without the leading whitespace.
 5852    cx.set_state(indoc! {"
 5853        const a: B = (
 5854            c(),
 5855            «d(
 5856                e,
 5857                f
 5858            )ˇ»
 5859        );
 5860    "});
 5861    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5862    cx.assert_editor_state(indoc! {"
 5863        const a: B = (
 5864            c(),
 5865            ˇ
 5866        );
 5867    "});
 5868
 5869    // Paste it at the same position.
 5870    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5871    cx.assert_editor_state(indoc! {"
 5872        const a: B = (
 5873            c(),
 5874            d(
 5875                e,
 5876                f
 5877 5878        );
 5879    "});
 5880
 5881    // Paste it at a line with a lower indent level.
 5882    cx.set_state(indoc! {"
 5883        ˇ
 5884        const a: B = (
 5885            c(),
 5886        );
 5887    "});
 5888    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5889    cx.assert_editor_state(indoc! {"
 5890        d(
 5891            e,
 5892            f
 5893 5894        const a: B = (
 5895            c(),
 5896        );
 5897    "});
 5898
 5899    // Cut an indented block, with the leading whitespace.
 5900    cx.set_state(indoc! {"
 5901        const a: B = (
 5902            c(),
 5903        «    d(
 5904                e,
 5905                f
 5906            )
 5907        ˇ»);
 5908    "});
 5909    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5910    cx.assert_editor_state(indoc! {"
 5911        const a: B = (
 5912            c(),
 5913        ˇ);
 5914    "});
 5915
 5916    // Paste it at the same position.
 5917    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5918    cx.assert_editor_state(indoc! {"
 5919        const a: B = (
 5920            c(),
 5921            d(
 5922                e,
 5923                f
 5924            )
 5925        ˇ);
 5926    "});
 5927
 5928    // Paste it at a line with a higher indent level.
 5929    cx.set_state(indoc! {"
 5930        const a: B = (
 5931            c(),
 5932            d(
 5933                e,
 5934 5935            )
 5936        );
 5937    "});
 5938    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5939    cx.assert_editor_state(indoc! {"
 5940        const a: B = (
 5941            c(),
 5942            d(
 5943                e,
 5944                f    d(
 5945                    e,
 5946                    f
 5947                )
 5948        ˇ
 5949            )
 5950        );
 5951    "});
 5952
 5953    // Copy an indented block, starting mid-line
 5954    cx.set_state(indoc! {"
 5955        const a: B = (
 5956            c(),
 5957            somethin«g(
 5958                e,
 5959                f
 5960            )ˇ»
 5961        );
 5962    "});
 5963    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5964
 5965    // Paste it on a line with a lower indent level
 5966    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5967    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5968    cx.assert_editor_state(indoc! {"
 5969        const a: B = (
 5970            c(),
 5971            something(
 5972                e,
 5973                f
 5974            )
 5975        );
 5976        g(
 5977            e,
 5978            f
 5979"});
 5980}
 5981
 5982#[gpui::test]
 5983async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5984    init_test(cx, |_| {});
 5985
 5986    cx.write_to_clipboard(ClipboardItem::new_string(
 5987        "    d(\n        e\n    );\n".into(),
 5988    ));
 5989
 5990    let mut cx = EditorTestContext::new(cx).await;
 5991    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5992
 5993    cx.set_state(indoc! {"
 5994        fn a() {
 5995            b();
 5996            if c() {
 5997                ˇ
 5998            }
 5999        }
 6000    "});
 6001
 6002    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6003    cx.assert_editor_state(indoc! {"
 6004        fn a() {
 6005            b();
 6006            if c() {
 6007                d(
 6008                    e
 6009                );
 6010        ˇ
 6011            }
 6012        }
 6013    "});
 6014
 6015    cx.set_state(indoc! {"
 6016        fn a() {
 6017            b();
 6018            ˇ
 6019        }
 6020    "});
 6021
 6022    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6023    cx.assert_editor_state(indoc! {"
 6024        fn a() {
 6025            b();
 6026            d(
 6027                e
 6028            );
 6029        ˇ
 6030        }
 6031    "});
 6032}
 6033
 6034#[gpui::test]
 6035fn test_select_all(cx: &mut TestAppContext) {
 6036    init_test(cx, |_| {});
 6037
 6038    let editor = cx.add_window(|window, cx| {
 6039        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6040        build_editor(buffer, window, cx)
 6041    });
 6042    _ = editor.update(cx, |editor, window, cx| {
 6043        editor.select_all(&SelectAll, window, cx);
 6044        assert_eq!(
 6045            editor.selections.display_ranges(cx),
 6046            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6047        );
 6048    });
 6049}
 6050
 6051#[gpui::test]
 6052fn test_select_line(cx: &mut TestAppContext) {
 6053    init_test(cx, |_| {});
 6054
 6055    let editor = cx.add_window(|window, cx| {
 6056        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6057        build_editor(buffer, window, cx)
 6058    });
 6059    _ = editor.update(cx, |editor, window, cx| {
 6060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6061            s.select_display_ranges([
 6062                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6063                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6064                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6065                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6066            ])
 6067        });
 6068        editor.select_line(&SelectLine, window, cx);
 6069        assert_eq!(
 6070            editor.selections.display_ranges(cx),
 6071            vec![
 6072                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6073                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6074            ]
 6075        );
 6076    });
 6077
 6078    _ = editor.update(cx, |editor, window, cx| {
 6079        editor.select_line(&SelectLine, window, cx);
 6080        assert_eq!(
 6081            editor.selections.display_ranges(cx),
 6082            vec![
 6083                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6084                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6085            ]
 6086        );
 6087    });
 6088
 6089    _ = editor.update(cx, |editor, window, cx| {
 6090        editor.select_line(&SelectLine, window, cx);
 6091        assert_eq!(
 6092            editor.selections.display_ranges(cx),
 6093            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6094        );
 6095    });
 6096}
 6097
 6098#[gpui::test]
 6099async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6100    init_test(cx, |_| {});
 6101    let mut cx = EditorTestContext::new(cx).await;
 6102
 6103    #[track_caller]
 6104    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6105        cx.set_state(initial_state);
 6106        cx.update_editor(|e, window, cx| {
 6107            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6108        });
 6109        cx.assert_editor_state(expected_state);
 6110    }
 6111
 6112    // Selection starts and ends at the middle of lines, left-to-right
 6113    test(
 6114        &mut cx,
 6115        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6116        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6117    );
 6118    // Same thing, right-to-left
 6119    test(
 6120        &mut cx,
 6121        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6122        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6123    );
 6124
 6125    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6126    test(
 6127        &mut cx,
 6128        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6129        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6130    );
 6131    // Same thing, right-to-left
 6132    test(
 6133        &mut cx,
 6134        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6135        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6136    );
 6137
 6138    // Whole buffer, left-to-right, last line ends with newline
 6139    test(
 6140        &mut cx,
 6141        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6142        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6143    );
 6144    // Same thing, right-to-left
 6145    test(
 6146        &mut cx,
 6147        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6148        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6149    );
 6150
 6151    // Starts at the end of a line, ends at the start of another
 6152    test(
 6153        &mut cx,
 6154        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6155        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6156    );
 6157}
 6158
 6159#[gpui::test]
 6160async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6161    init_test(cx, |_| {});
 6162
 6163    let editor = cx.add_window(|window, cx| {
 6164        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6165        build_editor(buffer, window, cx)
 6166    });
 6167
 6168    // setup
 6169    _ = editor.update(cx, |editor, window, cx| {
 6170        editor.fold_creases(
 6171            vec![
 6172                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6173                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6174                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6175            ],
 6176            true,
 6177            window,
 6178            cx,
 6179        );
 6180        assert_eq!(
 6181            editor.display_text(cx),
 6182            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6183        );
 6184    });
 6185
 6186    _ = editor.update(cx, |editor, window, cx| {
 6187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6188            s.select_display_ranges([
 6189                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6190                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6191                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6192                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6193            ])
 6194        });
 6195        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6196        assert_eq!(
 6197            editor.display_text(cx),
 6198            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6199        );
 6200    });
 6201    EditorTestContext::for_editor(editor, cx)
 6202        .await
 6203        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6204
 6205    _ = editor.update(cx, |editor, window, cx| {
 6206        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6207            s.select_display_ranges([
 6208                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6209            ])
 6210        });
 6211        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6212        assert_eq!(
 6213            editor.display_text(cx),
 6214            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6215        );
 6216        assert_eq!(
 6217            editor.selections.display_ranges(cx),
 6218            [
 6219                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6220                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6221                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6222                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6223                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6224                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6225                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6226            ]
 6227        );
 6228    });
 6229    EditorTestContext::for_editor(editor, cx)
 6230        .await
 6231        .assert_editor_state(
 6232            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6233        );
 6234}
 6235
 6236#[gpui::test]
 6237async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6238    init_test(cx, |_| {});
 6239
 6240    let mut cx = EditorTestContext::new(cx).await;
 6241
 6242    cx.set_state(indoc!(
 6243        r#"abc
 6244           defˇghi
 6245
 6246           jk
 6247           nlmo
 6248           "#
 6249    ));
 6250
 6251    cx.update_editor(|editor, window, cx| {
 6252        editor.add_selection_above(&Default::default(), window, cx);
 6253    });
 6254
 6255    cx.assert_editor_state(indoc!(
 6256        r#"abcˇ
 6257           defˇghi
 6258
 6259           jk
 6260           nlmo
 6261           "#
 6262    ));
 6263
 6264    cx.update_editor(|editor, window, cx| {
 6265        editor.add_selection_above(&Default::default(), window, cx);
 6266    });
 6267
 6268    cx.assert_editor_state(indoc!(
 6269        r#"abcˇ
 6270            defˇghi
 6271
 6272            jk
 6273            nlmo
 6274            "#
 6275    ));
 6276
 6277    cx.update_editor(|editor, window, cx| {
 6278        editor.add_selection_below(&Default::default(), window, cx);
 6279    });
 6280
 6281    cx.assert_editor_state(indoc!(
 6282        r#"abc
 6283           defˇghi
 6284
 6285           jk
 6286           nlmo
 6287           "#
 6288    ));
 6289
 6290    cx.update_editor(|editor, window, cx| {
 6291        editor.undo_selection(&Default::default(), window, cx);
 6292    });
 6293
 6294    cx.assert_editor_state(indoc!(
 6295        r#"abcˇ
 6296           defˇghi
 6297
 6298           jk
 6299           nlmo
 6300           "#
 6301    ));
 6302
 6303    cx.update_editor(|editor, window, cx| {
 6304        editor.redo_selection(&Default::default(), window, cx);
 6305    });
 6306
 6307    cx.assert_editor_state(indoc!(
 6308        r#"abc
 6309           defˇghi
 6310
 6311           jk
 6312           nlmo
 6313           "#
 6314    ));
 6315
 6316    cx.update_editor(|editor, window, cx| {
 6317        editor.add_selection_below(&Default::default(), window, cx);
 6318    });
 6319
 6320    cx.assert_editor_state(indoc!(
 6321        r#"abc
 6322           defˇghi
 6323           ˇ
 6324           jk
 6325           nlmo
 6326           "#
 6327    ));
 6328
 6329    cx.update_editor(|editor, window, cx| {
 6330        editor.add_selection_below(&Default::default(), window, cx);
 6331    });
 6332
 6333    cx.assert_editor_state(indoc!(
 6334        r#"abc
 6335           defˇghi
 6336           ˇ
 6337           jkˇ
 6338           nlmo
 6339           "#
 6340    ));
 6341
 6342    cx.update_editor(|editor, window, cx| {
 6343        editor.add_selection_below(&Default::default(), window, cx);
 6344    });
 6345
 6346    cx.assert_editor_state(indoc!(
 6347        r#"abc
 6348           defˇghi
 6349           ˇ
 6350           jkˇ
 6351           nlmˇo
 6352           "#
 6353    ));
 6354
 6355    cx.update_editor(|editor, window, cx| {
 6356        editor.add_selection_below(&Default::default(), window, cx);
 6357    });
 6358
 6359    cx.assert_editor_state(indoc!(
 6360        r#"abc
 6361           defˇghi
 6362           ˇ
 6363           jkˇ
 6364           nlmˇo
 6365           ˇ"#
 6366    ));
 6367
 6368    // change selections
 6369    cx.set_state(indoc!(
 6370        r#"abc
 6371           def«ˇg»hi
 6372
 6373           jk
 6374           nlmo
 6375           "#
 6376    ));
 6377
 6378    cx.update_editor(|editor, window, cx| {
 6379        editor.add_selection_below(&Default::default(), window, cx);
 6380    });
 6381
 6382    cx.assert_editor_state(indoc!(
 6383        r#"abc
 6384           def«ˇg»hi
 6385
 6386           jk
 6387           nlm«ˇo»
 6388           "#
 6389    ));
 6390
 6391    cx.update_editor(|editor, window, cx| {
 6392        editor.add_selection_below(&Default::default(), window, cx);
 6393    });
 6394
 6395    cx.assert_editor_state(indoc!(
 6396        r#"abc
 6397           def«ˇg»hi
 6398
 6399           jk
 6400           nlm«ˇo»
 6401           "#
 6402    ));
 6403
 6404    cx.update_editor(|editor, window, cx| {
 6405        editor.add_selection_above(&Default::default(), window, cx);
 6406    });
 6407
 6408    cx.assert_editor_state(indoc!(
 6409        r#"abc
 6410           def«ˇg»hi
 6411
 6412           jk
 6413           nlmo
 6414           "#
 6415    ));
 6416
 6417    cx.update_editor(|editor, window, cx| {
 6418        editor.add_selection_above(&Default::default(), window, cx);
 6419    });
 6420
 6421    cx.assert_editor_state(indoc!(
 6422        r#"abc
 6423           def«ˇg»hi
 6424
 6425           jk
 6426           nlmo
 6427           "#
 6428    ));
 6429
 6430    // Change selections again
 6431    cx.set_state(indoc!(
 6432        r#"a«bc
 6433           defgˇ»hi
 6434
 6435           jk
 6436           nlmo
 6437           "#
 6438    ));
 6439
 6440    cx.update_editor(|editor, window, cx| {
 6441        editor.add_selection_below(&Default::default(), window, cx);
 6442    });
 6443
 6444    cx.assert_editor_state(indoc!(
 6445        r#"a«bcˇ»
 6446           d«efgˇ»hi
 6447
 6448           j«kˇ»
 6449           nlmo
 6450           "#
 6451    ));
 6452
 6453    cx.update_editor(|editor, window, cx| {
 6454        editor.add_selection_below(&Default::default(), window, cx);
 6455    });
 6456    cx.assert_editor_state(indoc!(
 6457        r#"a«bcˇ»
 6458           d«efgˇ»hi
 6459
 6460           j«kˇ»
 6461           n«lmoˇ»
 6462           "#
 6463    ));
 6464    cx.update_editor(|editor, window, cx| {
 6465        editor.add_selection_above(&Default::default(), window, cx);
 6466    });
 6467
 6468    cx.assert_editor_state(indoc!(
 6469        r#"a«bcˇ»
 6470           d«efgˇ»hi
 6471
 6472           j«kˇ»
 6473           nlmo
 6474           "#
 6475    ));
 6476
 6477    // Change selections again
 6478    cx.set_state(indoc!(
 6479        r#"abc
 6480           d«ˇefghi
 6481
 6482           jk
 6483           nlm»o
 6484           "#
 6485    ));
 6486
 6487    cx.update_editor(|editor, window, cx| {
 6488        editor.add_selection_above(&Default::default(), window, cx);
 6489    });
 6490
 6491    cx.assert_editor_state(indoc!(
 6492        r#"a«ˇbc»
 6493           d«ˇef»ghi
 6494
 6495           j«ˇk»
 6496           n«ˇlm»o
 6497           "#
 6498    ));
 6499
 6500    cx.update_editor(|editor, window, cx| {
 6501        editor.add_selection_below(&Default::default(), window, cx);
 6502    });
 6503
 6504    cx.assert_editor_state(indoc!(
 6505        r#"abc
 6506           d«ˇef»ghi
 6507
 6508           j«ˇk»
 6509           n«ˇlm»o
 6510           "#
 6511    ));
 6512}
 6513
 6514#[gpui::test]
 6515async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6516    init_test(cx, |_| {});
 6517    let mut cx = EditorTestContext::new(cx).await;
 6518
 6519    cx.set_state(indoc!(
 6520        r#"line onˇe
 6521           liˇne two
 6522           line three
 6523           line four"#
 6524    ));
 6525
 6526    cx.update_editor(|editor, window, cx| {
 6527        editor.add_selection_below(&Default::default(), window, cx);
 6528    });
 6529
 6530    // test multiple cursors expand in the same direction
 6531    cx.assert_editor_state(indoc!(
 6532        r#"line onˇe
 6533           liˇne twˇo
 6534           liˇne three
 6535           line four"#
 6536    ));
 6537
 6538    cx.update_editor(|editor, window, cx| {
 6539        editor.add_selection_below(&Default::default(), window, cx);
 6540    });
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.add_selection_below(&Default::default(), window, cx);
 6544    });
 6545
 6546    // test multiple cursors expand below overflow
 6547    cx.assert_editor_state(indoc!(
 6548        r#"line onˇe
 6549           liˇne twˇo
 6550           liˇne thˇree
 6551           liˇne foˇur"#
 6552    ));
 6553
 6554    cx.update_editor(|editor, window, cx| {
 6555        editor.add_selection_above(&Default::default(), window, cx);
 6556    });
 6557
 6558    // test multiple cursors retrieves back correctly
 6559    cx.assert_editor_state(indoc!(
 6560        r#"line onˇe
 6561           liˇne twˇo
 6562           liˇne thˇree
 6563           line four"#
 6564    ));
 6565
 6566    cx.update_editor(|editor, window, cx| {
 6567        editor.add_selection_above(&Default::default(), window, cx);
 6568    });
 6569
 6570    cx.update_editor(|editor, window, cx| {
 6571        editor.add_selection_above(&Default::default(), window, cx);
 6572    });
 6573
 6574    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6575    cx.assert_editor_state(indoc!(
 6576        r#"liˇne onˇe
 6577           liˇne two
 6578           line three
 6579           line four"#
 6580    ));
 6581
 6582    cx.update_editor(|editor, window, cx| {
 6583        editor.undo_selection(&Default::default(), window, cx);
 6584    });
 6585
 6586    // test undo
 6587    cx.assert_editor_state(indoc!(
 6588        r#"line onˇe
 6589           liˇne twˇo
 6590           line three
 6591           line four"#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.redo_selection(&Default::default(), window, cx);
 6596    });
 6597
 6598    // test redo
 6599    cx.assert_editor_state(indoc!(
 6600        r#"liˇne onˇe
 6601           liˇne two
 6602           line three
 6603           line four"#
 6604    ));
 6605
 6606    cx.set_state(indoc!(
 6607        r#"abcd
 6608           ef«ghˇ»
 6609           ijkl
 6610           «mˇ»nop"#
 6611    ));
 6612
 6613    cx.update_editor(|editor, window, cx| {
 6614        editor.add_selection_above(&Default::default(), window, cx);
 6615    });
 6616
 6617    // test multiple selections expand in the same direction
 6618    cx.assert_editor_state(indoc!(
 6619        r#"ab«cdˇ»
 6620           ef«ghˇ»
 6621           «iˇ»jkl
 6622           «mˇ»nop"#
 6623    ));
 6624
 6625    cx.update_editor(|editor, window, cx| {
 6626        editor.add_selection_above(&Default::default(), window, cx);
 6627    });
 6628
 6629    // test multiple selection upward overflow
 6630    cx.assert_editor_state(indoc!(
 6631        r#"ab«cdˇ»
 6632           «eˇ»f«ghˇ»
 6633           «iˇ»jkl
 6634           «mˇ»nop"#
 6635    ));
 6636
 6637    cx.update_editor(|editor, window, cx| {
 6638        editor.add_selection_below(&Default::default(), window, cx);
 6639    });
 6640
 6641    // test multiple selection retrieves back correctly
 6642    cx.assert_editor_state(indoc!(
 6643        r#"abcd
 6644           ef«ghˇ»
 6645           «iˇ»jkl
 6646           «mˇ»nop"#
 6647    ));
 6648
 6649    cx.update_editor(|editor, window, cx| {
 6650        editor.add_selection_below(&Default::default(), window, cx);
 6651    });
 6652
 6653    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6654    cx.assert_editor_state(indoc!(
 6655        r#"abcd
 6656           ef«ghˇ»
 6657           ij«klˇ»
 6658           «mˇ»nop"#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.undo_selection(&Default::default(), window, cx);
 6663    });
 6664
 6665    // test undo
 6666    cx.assert_editor_state(indoc!(
 6667        r#"abcd
 6668           ef«ghˇ»
 6669           «iˇ»jkl
 6670           «mˇ»nop"#
 6671    ));
 6672
 6673    cx.update_editor(|editor, window, cx| {
 6674        editor.redo_selection(&Default::default(), window, cx);
 6675    });
 6676
 6677    // test redo
 6678    cx.assert_editor_state(indoc!(
 6679        r#"abcd
 6680           ef«ghˇ»
 6681           ij«klˇ»
 6682           «mˇ»nop"#
 6683    ));
 6684}
 6685
 6686#[gpui::test]
 6687async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6688    init_test(cx, |_| {});
 6689    let mut cx = EditorTestContext::new(cx).await;
 6690
 6691    cx.set_state(indoc!(
 6692        r#"line onˇe
 6693           liˇne two
 6694           line three
 6695           line four"#
 6696    ));
 6697
 6698    cx.update_editor(|editor, window, cx| {
 6699        editor.add_selection_below(&Default::default(), window, cx);
 6700        editor.add_selection_below(&Default::default(), window, cx);
 6701        editor.add_selection_below(&Default::default(), window, cx);
 6702    });
 6703
 6704    // initial state with two multi cursor groups
 6705    cx.assert_editor_state(indoc!(
 6706        r#"line onˇe
 6707           liˇne twˇo
 6708           liˇne thˇree
 6709           liˇne foˇur"#
 6710    ));
 6711
 6712    // add single cursor in middle - simulate opt click
 6713    cx.update_editor(|editor, window, cx| {
 6714        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6715        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6716        editor.end_selection(window, cx);
 6717    });
 6718
 6719    cx.assert_editor_state(indoc!(
 6720        r#"line onˇe
 6721           liˇne twˇo
 6722           liˇneˇ thˇree
 6723           liˇne foˇur"#
 6724    ));
 6725
 6726    cx.update_editor(|editor, window, cx| {
 6727        editor.add_selection_above(&Default::default(), window, cx);
 6728    });
 6729
 6730    // test new added selection expands above and existing selection shrinks
 6731    cx.assert_editor_state(indoc!(
 6732        r#"line onˇe
 6733           liˇneˇ twˇo
 6734           liˇneˇ thˇree
 6735           line four"#
 6736    ));
 6737
 6738    cx.update_editor(|editor, window, cx| {
 6739        editor.add_selection_above(&Default::default(), window, cx);
 6740    });
 6741
 6742    // test new added selection expands above and existing selection shrinks
 6743    cx.assert_editor_state(indoc!(
 6744        r#"lineˇ onˇe
 6745           liˇneˇ twˇo
 6746           lineˇ three
 6747           line four"#
 6748    ));
 6749
 6750    // intial state with two selection groups
 6751    cx.set_state(indoc!(
 6752        r#"abcd
 6753           ef«ghˇ»
 6754           ijkl
 6755           «mˇ»nop"#
 6756    ));
 6757
 6758    cx.update_editor(|editor, window, cx| {
 6759        editor.add_selection_above(&Default::default(), window, cx);
 6760        editor.add_selection_above(&Default::default(), window, cx);
 6761    });
 6762
 6763    cx.assert_editor_state(indoc!(
 6764        r#"ab«cdˇ»
 6765           «eˇ»f«ghˇ»
 6766           «iˇ»jkl
 6767           «mˇ»nop"#
 6768    ));
 6769
 6770    // add single selection in middle - simulate opt drag
 6771    cx.update_editor(|editor, window, cx| {
 6772        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6773        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6774        editor.update_selection(
 6775            DisplayPoint::new(DisplayRow(2), 4),
 6776            0,
 6777            gpui::Point::<f32>::default(),
 6778            window,
 6779            cx,
 6780        );
 6781        editor.end_selection(window, cx);
 6782    });
 6783
 6784    cx.assert_editor_state(indoc!(
 6785        r#"ab«cdˇ»
 6786           «eˇ»f«ghˇ»
 6787           «iˇ»jk«lˇ»
 6788           «mˇ»nop"#
 6789    ));
 6790
 6791    cx.update_editor(|editor, window, cx| {
 6792        editor.add_selection_below(&Default::default(), window, cx);
 6793    });
 6794
 6795    // test new added selection expands below, others shrinks from above
 6796    cx.assert_editor_state(indoc!(
 6797        r#"abcd
 6798           ef«ghˇ»
 6799           «iˇ»jk«lˇ»
 6800           «mˇ»no«pˇ»"#
 6801    ));
 6802}
 6803
 6804#[gpui::test]
 6805async fn test_select_next(cx: &mut TestAppContext) {
 6806    init_test(cx, |_| {});
 6807
 6808    let mut cx = EditorTestContext::new(cx).await;
 6809    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6810
 6811    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6812        .unwrap();
 6813    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6814
 6815    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6816        .unwrap();
 6817    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6818
 6819    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6820    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6821
 6822    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6823    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6824
 6825    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6826        .unwrap();
 6827    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6828
 6829    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6830        .unwrap();
 6831    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6832
 6833    // Test selection direction should be preserved
 6834    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6835
 6836    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6837        .unwrap();
 6838    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6839}
 6840
 6841#[gpui::test]
 6842async fn test_select_all_matches(cx: &mut TestAppContext) {
 6843    init_test(cx, |_| {});
 6844
 6845    let mut cx = EditorTestContext::new(cx).await;
 6846
 6847    // Test caret-only selections
 6848    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6849    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6850        .unwrap();
 6851    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6852
 6853    // Test left-to-right selections
 6854    cx.set_state("abc\n«abcˇ»\nabc");
 6855    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6856        .unwrap();
 6857    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6858
 6859    // Test right-to-left selections
 6860    cx.set_state("abc\n«ˇabc»\nabc");
 6861    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6862        .unwrap();
 6863    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6864
 6865    // Test selecting whitespace with caret selection
 6866    cx.set_state("abc\nˇ   abc\nabc");
 6867    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6868        .unwrap();
 6869    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6870
 6871    // Test selecting whitespace with left-to-right selection
 6872    cx.set_state("abc\n«ˇ  »abc\nabc");
 6873    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6874        .unwrap();
 6875    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6876
 6877    // Test no matches with right-to-left selection
 6878    cx.set_state("abc\n«  ˇ»abc\nabc");
 6879    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6880        .unwrap();
 6881    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6882
 6883    // Test with a single word and clip_at_line_ends=true (#29823)
 6884    cx.set_state("aˇbc");
 6885    cx.update_editor(|e, window, cx| {
 6886        e.set_clip_at_line_ends(true, cx);
 6887        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6888        e.set_clip_at_line_ends(false, cx);
 6889    });
 6890    cx.assert_editor_state("«abcˇ»");
 6891}
 6892
 6893#[gpui::test]
 6894async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6895    init_test(cx, |_| {});
 6896
 6897    let mut cx = EditorTestContext::new(cx).await;
 6898
 6899    let large_body_1 = "\nd".repeat(200);
 6900    let large_body_2 = "\ne".repeat(200);
 6901
 6902    cx.set_state(&format!(
 6903        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6904    ));
 6905    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6906        let scroll_position = editor.scroll_position(cx);
 6907        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6908        scroll_position
 6909    });
 6910
 6911    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6912        .unwrap();
 6913    cx.assert_editor_state(&format!(
 6914        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6915    ));
 6916    let scroll_position_after_selection =
 6917        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6918    assert_eq!(
 6919        initial_scroll_position, scroll_position_after_selection,
 6920        "Scroll position should not change after selecting all matches"
 6921    );
 6922}
 6923
 6924#[gpui::test]
 6925async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6926    init_test(cx, |_| {});
 6927
 6928    let mut cx = EditorLspTestContext::new_rust(
 6929        lsp::ServerCapabilities {
 6930            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6931            ..Default::default()
 6932        },
 6933        cx,
 6934    )
 6935    .await;
 6936
 6937    cx.set_state(indoc! {"
 6938        line 1
 6939        line 2
 6940        linˇe 3
 6941        line 4
 6942        line 5
 6943    "});
 6944
 6945    // Make an edit
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.handle_input("X", window, cx);
 6948    });
 6949
 6950    // Move cursor to a different position
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6953            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6954        });
 6955    });
 6956
 6957    cx.assert_editor_state(indoc! {"
 6958        line 1
 6959        line 2
 6960        linXe 3
 6961        line 4
 6962        liˇne 5
 6963    "});
 6964
 6965    cx.lsp
 6966        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6967            Ok(Some(vec![lsp::TextEdit::new(
 6968                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6969                "PREFIX ".to_string(),
 6970            )]))
 6971        });
 6972
 6973    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6974        .unwrap()
 6975        .await
 6976        .unwrap();
 6977
 6978    cx.assert_editor_state(indoc! {"
 6979        PREFIX line 1
 6980        line 2
 6981        linXe 3
 6982        line 4
 6983        liˇne 5
 6984    "});
 6985
 6986    // Undo formatting
 6987    cx.update_editor(|editor, window, cx| {
 6988        editor.undo(&Default::default(), window, cx);
 6989    });
 6990
 6991    // Verify cursor moved back to position after edit
 6992    cx.assert_editor_state(indoc! {"
 6993        line 1
 6994        line 2
 6995        linXˇe 3
 6996        line 4
 6997        line 5
 6998    "});
 6999}
 7000
 7001#[gpui::test]
 7002async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7003    init_test(cx, |_| {});
 7004
 7005    let mut cx = EditorTestContext::new(cx).await;
 7006
 7007    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7008    cx.update_editor(|editor, window, cx| {
 7009        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7010    });
 7011
 7012    cx.set_state(indoc! {"
 7013        line 1
 7014        line 2
 7015        linˇe 3
 7016        line 4
 7017        line 5
 7018        line 6
 7019        line 7
 7020        line 8
 7021        line 9
 7022        line 10
 7023    "});
 7024
 7025    let snapshot = cx.buffer_snapshot();
 7026    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7027
 7028    cx.update(|_, cx| {
 7029        provider.update(cx, |provider, _| {
 7030            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7031                id: None,
 7032                edits: vec![(edit_position..edit_position, "X".into())],
 7033                edit_preview: None,
 7034            }))
 7035        })
 7036    });
 7037
 7038    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7039    cx.update_editor(|editor, window, cx| {
 7040        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7041    });
 7042
 7043    cx.assert_editor_state(indoc! {"
 7044        line 1
 7045        line 2
 7046        lineXˇ 3
 7047        line 4
 7048        line 5
 7049        line 6
 7050        line 7
 7051        line 8
 7052        line 9
 7053        line 10
 7054    "});
 7055
 7056    cx.update_editor(|editor, window, cx| {
 7057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7058            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7059        });
 7060    });
 7061
 7062    cx.assert_editor_state(indoc! {"
 7063        line 1
 7064        line 2
 7065        lineX 3
 7066        line 4
 7067        line 5
 7068        line 6
 7069        line 7
 7070        line 8
 7071        line 9
 7072        liˇne 10
 7073    "});
 7074
 7075    cx.update_editor(|editor, window, cx| {
 7076        editor.undo(&Default::default(), window, cx);
 7077    });
 7078
 7079    cx.assert_editor_state(indoc! {"
 7080        line 1
 7081        line 2
 7082        lineˇ 3
 7083        line 4
 7084        line 5
 7085        line 6
 7086        line 7
 7087        line 8
 7088        line 9
 7089        line 10
 7090    "});
 7091}
 7092
 7093#[gpui::test]
 7094async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7095    init_test(cx, |_| {});
 7096
 7097    let mut cx = EditorTestContext::new(cx).await;
 7098    cx.set_state(
 7099        r#"let foo = 2;
 7100lˇet foo = 2;
 7101let fooˇ = 2;
 7102let foo = 2;
 7103let foo = ˇ2;"#,
 7104    );
 7105
 7106    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7107        .unwrap();
 7108    cx.assert_editor_state(
 7109        r#"let foo = 2;
 7110«letˇ» foo = 2;
 7111let «fooˇ» = 2;
 7112let foo = 2;
 7113let foo = «2ˇ»;"#,
 7114    );
 7115
 7116    // noop for multiple selections with different contents
 7117    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7118        .unwrap();
 7119    cx.assert_editor_state(
 7120        r#"let foo = 2;
 7121«letˇ» foo = 2;
 7122let «fooˇ» = 2;
 7123let foo = 2;
 7124let foo = «2ˇ»;"#,
 7125    );
 7126
 7127    // Test last selection direction should be preserved
 7128    cx.set_state(
 7129        r#"let foo = 2;
 7130let foo = 2;
 7131let «fooˇ» = 2;
 7132let «ˇfoo» = 2;
 7133let foo = 2;"#,
 7134    );
 7135
 7136    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7137        .unwrap();
 7138    cx.assert_editor_state(
 7139        r#"let foo = 2;
 7140let foo = 2;
 7141let «fooˇ» = 2;
 7142let «ˇfoo» = 2;
 7143let «ˇfoo» = 2;"#,
 7144    );
 7145}
 7146
 7147#[gpui::test]
 7148async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7149    init_test(cx, |_| {});
 7150
 7151    let mut cx =
 7152        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7153
 7154    cx.assert_editor_state(indoc! {"
 7155        ˇbbb
 7156        ccc
 7157
 7158        bbb
 7159        ccc
 7160        "});
 7161    cx.dispatch_action(SelectPrevious::default());
 7162    cx.assert_editor_state(indoc! {"
 7163                «bbbˇ»
 7164                ccc
 7165
 7166                bbb
 7167                ccc
 7168                "});
 7169    cx.dispatch_action(SelectPrevious::default());
 7170    cx.assert_editor_state(indoc! {"
 7171                «bbbˇ»
 7172                ccc
 7173
 7174                «bbbˇ»
 7175                ccc
 7176                "});
 7177}
 7178
 7179#[gpui::test]
 7180async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7181    init_test(cx, |_| {});
 7182
 7183    let mut cx = EditorTestContext::new(cx).await;
 7184    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7185
 7186    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7187        .unwrap();
 7188    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7189
 7190    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7191        .unwrap();
 7192    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7193
 7194    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7195    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7196
 7197    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7198    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7199
 7200    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7201        .unwrap();
 7202    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7203
 7204    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7205        .unwrap();
 7206    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7207}
 7208
 7209#[gpui::test]
 7210async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7211    init_test(cx, |_| {});
 7212
 7213    let mut cx = EditorTestContext::new(cx).await;
 7214    cx.set_state("");
 7215
 7216    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7217        .unwrap();
 7218    cx.assert_editor_state("«aˇ»");
 7219    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7220        .unwrap();
 7221    cx.assert_editor_state("«aˇ»");
 7222}
 7223
 7224#[gpui::test]
 7225async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7226    init_test(cx, |_| {});
 7227
 7228    let mut cx = EditorTestContext::new(cx).await;
 7229    cx.set_state(
 7230        r#"let foo = 2;
 7231lˇet foo = 2;
 7232let fooˇ = 2;
 7233let foo = 2;
 7234let foo = ˇ2;"#,
 7235    );
 7236
 7237    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7238        .unwrap();
 7239    cx.assert_editor_state(
 7240        r#"let foo = 2;
 7241«letˇ» foo = 2;
 7242let «fooˇ» = 2;
 7243let foo = 2;
 7244let foo = «2ˇ»;"#,
 7245    );
 7246
 7247    // noop for multiple selections with different contents
 7248    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7249        .unwrap();
 7250    cx.assert_editor_state(
 7251        r#"let foo = 2;
 7252«letˇ» foo = 2;
 7253let «fooˇ» = 2;
 7254let foo = 2;
 7255let foo = «2ˇ»;"#,
 7256    );
 7257}
 7258
 7259#[gpui::test]
 7260async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7261    init_test(cx, |_| {});
 7262
 7263    let mut cx = EditorTestContext::new(cx).await;
 7264    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7265
 7266    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7267        .unwrap();
 7268    // selection direction is preserved
 7269    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7270
 7271    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7272        .unwrap();
 7273    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7274
 7275    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7276    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7277
 7278    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7279    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7280
 7281    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7282        .unwrap();
 7283    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7284
 7285    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7286        .unwrap();
 7287    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7288}
 7289
 7290#[gpui::test]
 7291async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7292    init_test(cx, |_| {});
 7293
 7294    let language = Arc::new(Language::new(
 7295        LanguageConfig::default(),
 7296        Some(tree_sitter_rust::LANGUAGE.into()),
 7297    ));
 7298
 7299    let text = r#"
 7300        use mod1::mod2::{mod3, mod4};
 7301
 7302        fn fn_1(param1: bool, param2: &str) {
 7303            let var1 = "text";
 7304        }
 7305    "#
 7306    .unindent();
 7307
 7308    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7309    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7310    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7311
 7312    editor
 7313        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7314        .await;
 7315
 7316    editor.update_in(cx, |editor, window, cx| {
 7317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7318            s.select_display_ranges([
 7319                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7320                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7321                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7322            ]);
 7323        });
 7324        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7325    });
 7326    editor.update(cx, |editor, cx| {
 7327        assert_text_with_selections(
 7328            editor,
 7329            indoc! {r#"
 7330                use mod1::mod2::{mod3, «mod4ˇ»};
 7331
 7332                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7333                    let var1 = "«ˇtext»";
 7334                }
 7335            "#},
 7336            cx,
 7337        );
 7338    });
 7339
 7340    editor.update_in(cx, |editor, window, cx| {
 7341        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7342    });
 7343    editor.update(cx, |editor, cx| {
 7344        assert_text_with_selections(
 7345            editor,
 7346            indoc! {r#"
 7347                use mod1::mod2::«{mod3, mod4}ˇ»;
 7348
 7349                «ˇfn fn_1(param1: bool, param2: &str) {
 7350                    let var1 = "text";
 7351 7352            "#},
 7353            cx,
 7354        );
 7355    });
 7356
 7357    editor.update_in(cx, |editor, window, cx| {
 7358        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7359    });
 7360    assert_eq!(
 7361        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7362        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7363    );
 7364
 7365    // Trying to expand the selected syntax node one more time has no effect.
 7366    editor.update_in(cx, |editor, window, cx| {
 7367        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7368    });
 7369    assert_eq!(
 7370        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7371        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7372    );
 7373
 7374    editor.update_in(cx, |editor, window, cx| {
 7375        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7376    });
 7377    editor.update(cx, |editor, cx| {
 7378        assert_text_with_selections(
 7379            editor,
 7380            indoc! {r#"
 7381                use mod1::mod2::«{mod3, mod4}ˇ»;
 7382
 7383                «ˇfn fn_1(param1: bool, param2: &str) {
 7384                    let var1 = "text";
 7385 7386            "#},
 7387            cx,
 7388        );
 7389    });
 7390
 7391    editor.update_in(cx, |editor, window, cx| {
 7392        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7393    });
 7394    editor.update(cx, |editor, cx| {
 7395        assert_text_with_selections(
 7396            editor,
 7397            indoc! {r#"
 7398                use mod1::mod2::{mod3, «mod4ˇ»};
 7399
 7400                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7401                    let var1 = "«ˇtext»";
 7402                }
 7403            "#},
 7404            cx,
 7405        );
 7406    });
 7407
 7408    editor.update_in(cx, |editor, window, cx| {
 7409        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7410    });
 7411    editor.update(cx, |editor, cx| {
 7412        assert_text_with_selections(
 7413            editor,
 7414            indoc! {r#"
 7415                use mod1::mod2::{mod3, mo«ˇ»d4};
 7416
 7417                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7418                    let var1 = "te«ˇ»xt";
 7419                }
 7420            "#},
 7421            cx,
 7422        );
 7423    });
 7424
 7425    // Trying to shrink the selected syntax node one more time has no effect.
 7426    editor.update_in(cx, |editor, window, cx| {
 7427        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7428    });
 7429    editor.update_in(cx, |editor, _, cx| {
 7430        assert_text_with_selections(
 7431            editor,
 7432            indoc! {r#"
 7433                use mod1::mod2::{mod3, mo«ˇ»d4};
 7434
 7435                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7436                    let var1 = "te«ˇ»xt";
 7437                }
 7438            "#},
 7439            cx,
 7440        );
 7441    });
 7442
 7443    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7444    // a fold.
 7445    editor.update_in(cx, |editor, window, cx| {
 7446        editor.fold_creases(
 7447            vec![
 7448                Crease::simple(
 7449                    Point::new(0, 21)..Point::new(0, 24),
 7450                    FoldPlaceholder::test(),
 7451                ),
 7452                Crease::simple(
 7453                    Point::new(3, 20)..Point::new(3, 22),
 7454                    FoldPlaceholder::test(),
 7455                ),
 7456            ],
 7457            true,
 7458            window,
 7459            cx,
 7460        );
 7461        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7462    });
 7463    editor.update(cx, |editor, cx| {
 7464        assert_text_with_selections(
 7465            editor,
 7466            indoc! {r#"
 7467                use mod1::mod2::«{mod3, mod4}ˇ»;
 7468
 7469                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7470                    let var1 = "«ˇtext»";
 7471                }
 7472            "#},
 7473            cx,
 7474        );
 7475    });
 7476}
 7477
 7478#[gpui::test]
 7479async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7480    init_test(cx, |_| {});
 7481
 7482    let language = Arc::new(Language::new(
 7483        LanguageConfig::default(),
 7484        Some(tree_sitter_rust::LANGUAGE.into()),
 7485    ));
 7486
 7487    let text = "let a = 2;";
 7488
 7489    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7490    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7491    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7492
 7493    editor
 7494        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7495        .await;
 7496
 7497    // Test case 1: Cursor at end of word
 7498    editor.update_in(cx, |editor, window, cx| {
 7499        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7500            s.select_display_ranges([
 7501                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7502            ]);
 7503        });
 7504    });
 7505    editor.update(cx, |editor, cx| {
 7506        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7507    });
 7508    editor.update_in(cx, |editor, window, cx| {
 7509        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7510    });
 7511    editor.update(cx, |editor, cx| {
 7512        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7513    });
 7514    editor.update_in(cx, |editor, window, cx| {
 7515        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7516    });
 7517    editor.update(cx, |editor, cx| {
 7518        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7519    });
 7520
 7521    // Test case 2: Cursor at end of statement
 7522    editor.update_in(cx, |editor, window, cx| {
 7523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7524            s.select_display_ranges([
 7525                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7526            ]);
 7527        });
 7528    });
 7529    editor.update(cx, |editor, cx| {
 7530        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7531    });
 7532    editor.update_in(cx, |editor, window, cx| {
 7533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7534    });
 7535    editor.update(cx, |editor, cx| {
 7536        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7537    });
 7538}
 7539
 7540#[gpui::test]
 7541async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7542    init_test(cx, |_| {});
 7543
 7544    let language = Arc::new(Language::new(
 7545        LanguageConfig::default(),
 7546        Some(tree_sitter_rust::LANGUAGE.into()),
 7547    ));
 7548
 7549    let text = r#"
 7550        use mod1::mod2::{mod3, mod4};
 7551
 7552        fn fn_1(param1: bool, param2: &str) {
 7553            let var1 = "hello world";
 7554        }
 7555    "#
 7556    .unindent();
 7557
 7558    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7559    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7560    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7561
 7562    editor
 7563        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7564        .await;
 7565
 7566    // Test 1: Cursor on a letter of a string word
 7567    editor.update_in(cx, |editor, window, cx| {
 7568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7569            s.select_display_ranges([
 7570                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7571            ]);
 7572        });
 7573    });
 7574    editor.update_in(cx, |editor, window, cx| {
 7575        assert_text_with_selections(
 7576            editor,
 7577            indoc! {r#"
 7578                use mod1::mod2::{mod3, mod4};
 7579
 7580                fn fn_1(param1: bool, param2: &str) {
 7581                    let var1 = "hˇello world";
 7582                }
 7583            "#},
 7584            cx,
 7585        );
 7586        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7587        assert_text_with_selections(
 7588            editor,
 7589            indoc! {r#"
 7590                use mod1::mod2::{mod3, mod4};
 7591
 7592                fn fn_1(param1: bool, param2: &str) {
 7593                    let var1 = "«ˇhello» world";
 7594                }
 7595            "#},
 7596            cx,
 7597        );
 7598    });
 7599
 7600    // Test 2: Partial selection within a word
 7601    editor.update_in(cx, |editor, window, cx| {
 7602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7603            s.select_display_ranges([
 7604                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7605            ]);
 7606        });
 7607    });
 7608    editor.update_in(cx, |editor, window, cx| {
 7609        assert_text_with_selections(
 7610            editor,
 7611            indoc! {r#"
 7612                use mod1::mod2::{mod3, mod4};
 7613
 7614                fn fn_1(param1: bool, param2: &str) {
 7615                    let var1 = "h«elˇ»lo world";
 7616                }
 7617            "#},
 7618            cx,
 7619        );
 7620        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7621        assert_text_with_selections(
 7622            editor,
 7623            indoc! {r#"
 7624                use mod1::mod2::{mod3, mod4};
 7625
 7626                fn fn_1(param1: bool, param2: &str) {
 7627                    let var1 = "«ˇhello» world";
 7628                }
 7629            "#},
 7630            cx,
 7631        );
 7632    });
 7633
 7634    // Test 3: Complete word already selected
 7635    editor.update_in(cx, |editor, window, cx| {
 7636        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7637            s.select_display_ranges([
 7638                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7639            ]);
 7640        });
 7641    });
 7642    editor.update_in(cx, |editor, window, cx| {
 7643        assert_text_with_selections(
 7644            editor,
 7645            indoc! {r#"
 7646                use mod1::mod2::{mod3, mod4};
 7647
 7648                fn fn_1(param1: bool, param2: &str) {
 7649                    let var1 = "«helloˇ» world";
 7650                }
 7651            "#},
 7652            cx,
 7653        );
 7654        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7655        assert_text_with_selections(
 7656            editor,
 7657            indoc! {r#"
 7658                use mod1::mod2::{mod3, mod4};
 7659
 7660                fn fn_1(param1: bool, param2: &str) {
 7661                    let var1 = "«hello worldˇ»";
 7662                }
 7663            "#},
 7664            cx,
 7665        );
 7666    });
 7667
 7668    // Test 4: Selection spanning across words
 7669    editor.update_in(cx, |editor, window, cx| {
 7670        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7671            s.select_display_ranges([
 7672                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7673            ]);
 7674        });
 7675    });
 7676    editor.update_in(cx, |editor, window, cx| {
 7677        assert_text_with_selections(
 7678            editor,
 7679            indoc! {r#"
 7680                use mod1::mod2::{mod3, mod4};
 7681
 7682                fn fn_1(param1: bool, param2: &str) {
 7683                    let var1 = "hel«lo woˇ»rld";
 7684                }
 7685            "#},
 7686            cx,
 7687        );
 7688        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7689        assert_text_with_selections(
 7690            editor,
 7691            indoc! {r#"
 7692                use mod1::mod2::{mod3, mod4};
 7693
 7694                fn fn_1(param1: bool, param2: &str) {
 7695                    let var1 = "«ˇhello world»";
 7696                }
 7697            "#},
 7698            cx,
 7699        );
 7700    });
 7701
 7702    // Test 5: Expansion beyond string
 7703    editor.update_in(cx, |editor, window, cx| {
 7704        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7705        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7706        assert_text_with_selections(
 7707            editor,
 7708            indoc! {r#"
 7709                use mod1::mod2::{mod3, mod4};
 7710
 7711                fn fn_1(param1: bool, param2: &str) {
 7712                    «ˇlet var1 = "hello world";»
 7713                }
 7714            "#},
 7715            cx,
 7716        );
 7717    });
 7718}
 7719
 7720#[gpui::test]
 7721async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7722    init_test(cx, |_| {});
 7723
 7724    let base_text = r#"
 7725        impl A {
 7726            // this is an uncommitted comment
 7727
 7728            fn b() {
 7729                c();
 7730            }
 7731
 7732            // this is another uncommitted comment
 7733
 7734            fn d() {
 7735                // e
 7736                // f
 7737            }
 7738        }
 7739
 7740        fn g() {
 7741            // h
 7742        }
 7743    "#
 7744    .unindent();
 7745
 7746    let text = r#"
 7747        ˇimpl A {
 7748
 7749            fn b() {
 7750                c();
 7751            }
 7752
 7753            fn d() {
 7754                // e
 7755                // f
 7756            }
 7757        }
 7758
 7759        fn g() {
 7760            // h
 7761        }
 7762    "#
 7763    .unindent();
 7764
 7765    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7766    cx.set_state(&text);
 7767    cx.set_head_text(&base_text);
 7768    cx.update_editor(|editor, window, cx| {
 7769        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7770    });
 7771
 7772    cx.assert_state_with_diff(
 7773        "
 7774        ˇimpl A {
 7775      -     // this is an uncommitted comment
 7776
 7777            fn b() {
 7778                c();
 7779            }
 7780
 7781      -     // this is another uncommitted comment
 7782      -
 7783            fn d() {
 7784                // e
 7785                // f
 7786            }
 7787        }
 7788
 7789        fn g() {
 7790            // h
 7791        }
 7792    "
 7793        .unindent(),
 7794    );
 7795
 7796    let expected_display_text = "
 7797        impl A {
 7798            // this is an uncommitted comment
 7799
 7800            fn b() {
 7801 7802            }
 7803
 7804            // this is another uncommitted comment
 7805
 7806            fn d() {
 7807 7808            }
 7809        }
 7810
 7811        fn g() {
 7812 7813        }
 7814        "
 7815    .unindent();
 7816
 7817    cx.update_editor(|editor, window, cx| {
 7818        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7819        assert_eq!(editor.display_text(cx), expected_display_text);
 7820    });
 7821}
 7822
 7823#[gpui::test]
 7824async fn test_autoindent(cx: &mut TestAppContext) {
 7825    init_test(cx, |_| {});
 7826
 7827    let language = Arc::new(
 7828        Language::new(
 7829            LanguageConfig {
 7830                brackets: BracketPairConfig {
 7831                    pairs: vec![
 7832                        BracketPair {
 7833                            start: "{".to_string(),
 7834                            end: "}".to_string(),
 7835                            close: false,
 7836                            surround: false,
 7837                            newline: true,
 7838                        },
 7839                        BracketPair {
 7840                            start: "(".to_string(),
 7841                            end: ")".to_string(),
 7842                            close: false,
 7843                            surround: false,
 7844                            newline: true,
 7845                        },
 7846                    ],
 7847                    ..Default::default()
 7848                },
 7849                ..Default::default()
 7850            },
 7851            Some(tree_sitter_rust::LANGUAGE.into()),
 7852        )
 7853        .with_indents_query(
 7854            r#"
 7855                (_ "(" ")" @end) @indent
 7856                (_ "{" "}" @end) @indent
 7857            "#,
 7858        )
 7859        .unwrap(),
 7860    );
 7861
 7862    let text = "fn a() {}";
 7863
 7864    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7865    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7866    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7867    editor
 7868        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7869        .await;
 7870
 7871    editor.update_in(cx, |editor, window, cx| {
 7872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7873            s.select_ranges([5..5, 8..8, 9..9])
 7874        });
 7875        editor.newline(&Newline, window, cx);
 7876        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7877        assert_eq!(
 7878            editor.selections.ranges(cx),
 7879            &[
 7880                Point::new(1, 4)..Point::new(1, 4),
 7881                Point::new(3, 4)..Point::new(3, 4),
 7882                Point::new(5, 0)..Point::new(5, 0)
 7883            ]
 7884        );
 7885    });
 7886}
 7887
 7888#[gpui::test]
 7889async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7890    init_test(cx, |_| {});
 7891
 7892    {
 7893        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7894        cx.set_state(indoc! {"
 7895            impl A {
 7896
 7897                fn b() {}
 7898
 7899            «fn c() {
 7900
 7901            }ˇ»
 7902            }
 7903        "});
 7904
 7905        cx.update_editor(|editor, window, cx| {
 7906            editor.autoindent(&Default::default(), window, cx);
 7907        });
 7908
 7909        cx.assert_editor_state(indoc! {"
 7910            impl A {
 7911
 7912                fn b() {}
 7913
 7914                «fn c() {
 7915
 7916                }ˇ»
 7917            }
 7918        "});
 7919    }
 7920
 7921    {
 7922        let mut cx = EditorTestContext::new_multibuffer(
 7923            cx,
 7924            [indoc! { "
 7925                impl A {
 7926                «
 7927                // a
 7928                fn b(){}
 7929                »
 7930                «
 7931                    }
 7932                    fn c(){}
 7933                »
 7934            "}],
 7935        );
 7936
 7937        let buffer = cx.update_editor(|editor, _, cx| {
 7938            let buffer = editor.buffer().update(cx, |buffer, _| {
 7939                buffer.all_buffers().iter().next().unwrap().clone()
 7940            });
 7941            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7942            buffer
 7943        });
 7944
 7945        cx.run_until_parked();
 7946        cx.update_editor(|editor, window, cx| {
 7947            editor.select_all(&Default::default(), window, cx);
 7948            editor.autoindent(&Default::default(), window, cx)
 7949        });
 7950        cx.run_until_parked();
 7951
 7952        cx.update(|_, cx| {
 7953            assert_eq!(
 7954                buffer.read(cx).text(),
 7955                indoc! { "
 7956                    impl A {
 7957
 7958                        // a
 7959                        fn b(){}
 7960
 7961
 7962                    }
 7963                    fn c(){}
 7964
 7965                " }
 7966            )
 7967        });
 7968    }
 7969}
 7970
 7971#[gpui::test]
 7972async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7973    init_test(cx, |_| {});
 7974
 7975    let mut cx = EditorTestContext::new(cx).await;
 7976
 7977    let language = Arc::new(Language::new(
 7978        LanguageConfig {
 7979            brackets: BracketPairConfig {
 7980                pairs: vec![
 7981                    BracketPair {
 7982                        start: "{".to_string(),
 7983                        end: "}".to_string(),
 7984                        close: true,
 7985                        surround: true,
 7986                        newline: true,
 7987                    },
 7988                    BracketPair {
 7989                        start: "(".to_string(),
 7990                        end: ")".to_string(),
 7991                        close: true,
 7992                        surround: true,
 7993                        newline: true,
 7994                    },
 7995                    BracketPair {
 7996                        start: "/*".to_string(),
 7997                        end: " */".to_string(),
 7998                        close: true,
 7999                        surround: true,
 8000                        newline: true,
 8001                    },
 8002                    BracketPair {
 8003                        start: "[".to_string(),
 8004                        end: "]".to_string(),
 8005                        close: false,
 8006                        surround: false,
 8007                        newline: true,
 8008                    },
 8009                    BracketPair {
 8010                        start: "\"".to_string(),
 8011                        end: "\"".to_string(),
 8012                        close: true,
 8013                        surround: true,
 8014                        newline: false,
 8015                    },
 8016                    BracketPair {
 8017                        start: "<".to_string(),
 8018                        end: ">".to_string(),
 8019                        close: false,
 8020                        surround: true,
 8021                        newline: true,
 8022                    },
 8023                ],
 8024                ..Default::default()
 8025            },
 8026            autoclose_before: "})]".to_string(),
 8027            ..Default::default()
 8028        },
 8029        Some(tree_sitter_rust::LANGUAGE.into()),
 8030    ));
 8031
 8032    cx.language_registry().add(language.clone());
 8033    cx.update_buffer(|buffer, cx| {
 8034        buffer.set_language(Some(language), cx);
 8035    });
 8036
 8037    cx.set_state(
 8038        &r#"
 8039            🏀ˇ
 8040            εˇ
 8041            ❤️ˇ
 8042        "#
 8043        .unindent(),
 8044    );
 8045
 8046    // autoclose multiple nested brackets at multiple cursors
 8047    cx.update_editor(|editor, window, cx| {
 8048        editor.handle_input("{", window, cx);
 8049        editor.handle_input("{", window, cx);
 8050        editor.handle_input("{", window, cx);
 8051    });
 8052    cx.assert_editor_state(
 8053        &"
 8054            🏀{{{ˇ}}}
 8055            ε{{{ˇ}}}
 8056            ❤️{{{ˇ}}}
 8057        "
 8058        .unindent(),
 8059    );
 8060
 8061    // insert a different closing bracket
 8062    cx.update_editor(|editor, window, cx| {
 8063        editor.handle_input(")", window, cx);
 8064    });
 8065    cx.assert_editor_state(
 8066        &"
 8067            🏀{{{)ˇ}}}
 8068            ε{{{)ˇ}}}
 8069            ❤️{{{)ˇ}}}
 8070        "
 8071        .unindent(),
 8072    );
 8073
 8074    // skip over the auto-closed brackets when typing a closing bracket
 8075    cx.update_editor(|editor, window, cx| {
 8076        editor.move_right(&MoveRight, window, cx);
 8077        editor.handle_input("}", window, cx);
 8078        editor.handle_input("}", window, cx);
 8079        editor.handle_input("}", window, cx);
 8080    });
 8081    cx.assert_editor_state(
 8082        &"
 8083            🏀{{{)}}}}ˇ
 8084            ε{{{)}}}}ˇ
 8085            ❤️{{{)}}}}ˇ
 8086        "
 8087        .unindent(),
 8088    );
 8089
 8090    // autoclose multi-character pairs
 8091    cx.set_state(
 8092        &"
 8093            ˇ
 8094            ˇ
 8095        "
 8096        .unindent(),
 8097    );
 8098    cx.update_editor(|editor, window, cx| {
 8099        editor.handle_input("/", window, cx);
 8100        editor.handle_input("*", window, cx);
 8101    });
 8102    cx.assert_editor_state(
 8103        &"
 8104            /*ˇ */
 8105            /*ˇ */
 8106        "
 8107        .unindent(),
 8108    );
 8109
 8110    // one cursor autocloses a multi-character pair, one cursor
 8111    // does not autoclose.
 8112    cx.set_state(
 8113        &"
 8114 8115            ˇ
 8116        "
 8117        .unindent(),
 8118    );
 8119    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8120    cx.assert_editor_state(
 8121        &"
 8122            /*ˇ */
 8123 8124        "
 8125        .unindent(),
 8126    );
 8127
 8128    // Don't autoclose if the next character isn't whitespace and isn't
 8129    // listed in the language's "autoclose_before" section.
 8130    cx.set_state("ˇa b");
 8131    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8132    cx.assert_editor_state("{ˇa b");
 8133
 8134    // Don't autoclose if `close` is false for the bracket pair
 8135    cx.set_state("ˇ");
 8136    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8137    cx.assert_editor_state("");
 8138
 8139    // Surround with brackets if text is selected
 8140    cx.set_state("«aˇ» b");
 8141    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8142    cx.assert_editor_state("{«aˇ»} b");
 8143
 8144    // Autoclose when not immediately after a word character
 8145    cx.set_state("a ˇ");
 8146    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8147    cx.assert_editor_state("a \"ˇ\"");
 8148
 8149    // Autoclose pair where the start and end characters are the same
 8150    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8151    cx.assert_editor_state("a \"\"ˇ");
 8152
 8153    // Don't autoclose when immediately after a word character
 8154    cx.set_state("");
 8155    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8156    cx.assert_editor_state("a\"ˇ");
 8157
 8158    // Do autoclose when after a non-word character
 8159    cx.set_state("");
 8160    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8161    cx.assert_editor_state("{\"ˇ\"");
 8162
 8163    // Non identical pairs autoclose regardless of preceding character
 8164    cx.set_state("");
 8165    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8166    cx.assert_editor_state("a{ˇ}");
 8167
 8168    // Don't autoclose pair if autoclose is disabled
 8169    cx.set_state("ˇ");
 8170    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8171    cx.assert_editor_state("");
 8172
 8173    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8174    cx.set_state("«aˇ» b");
 8175    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8176    cx.assert_editor_state("<«aˇ»> b");
 8177}
 8178
 8179#[gpui::test]
 8180async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8181    init_test(cx, |settings| {
 8182        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8183    });
 8184
 8185    let mut cx = EditorTestContext::new(cx).await;
 8186
 8187    let language = Arc::new(Language::new(
 8188        LanguageConfig {
 8189            brackets: BracketPairConfig {
 8190                pairs: vec![
 8191                    BracketPair {
 8192                        start: "{".to_string(),
 8193                        end: "}".to_string(),
 8194                        close: true,
 8195                        surround: true,
 8196                        newline: true,
 8197                    },
 8198                    BracketPair {
 8199                        start: "(".to_string(),
 8200                        end: ")".to_string(),
 8201                        close: true,
 8202                        surround: true,
 8203                        newline: true,
 8204                    },
 8205                    BracketPair {
 8206                        start: "[".to_string(),
 8207                        end: "]".to_string(),
 8208                        close: false,
 8209                        surround: false,
 8210                        newline: true,
 8211                    },
 8212                ],
 8213                ..Default::default()
 8214            },
 8215            autoclose_before: "})]".to_string(),
 8216            ..Default::default()
 8217        },
 8218        Some(tree_sitter_rust::LANGUAGE.into()),
 8219    ));
 8220
 8221    cx.language_registry().add(language.clone());
 8222    cx.update_buffer(|buffer, cx| {
 8223        buffer.set_language(Some(language), cx);
 8224    });
 8225
 8226    cx.set_state(
 8227        &"
 8228            ˇ
 8229            ˇ
 8230            ˇ
 8231        "
 8232        .unindent(),
 8233    );
 8234
 8235    // ensure only matching closing brackets are skipped over
 8236    cx.update_editor(|editor, window, cx| {
 8237        editor.handle_input("}", window, cx);
 8238        editor.move_left(&MoveLeft, window, cx);
 8239        editor.handle_input(")", window, cx);
 8240        editor.move_left(&MoveLeft, window, cx);
 8241    });
 8242    cx.assert_editor_state(
 8243        &"
 8244            ˇ)}
 8245            ˇ)}
 8246            ˇ)}
 8247        "
 8248        .unindent(),
 8249    );
 8250
 8251    // skip-over closing brackets at multiple cursors
 8252    cx.update_editor(|editor, window, cx| {
 8253        editor.handle_input(")", window, cx);
 8254        editor.handle_input("}", window, cx);
 8255    });
 8256    cx.assert_editor_state(
 8257        &"
 8258            )}ˇ
 8259            )}ˇ
 8260            )}ˇ
 8261        "
 8262        .unindent(),
 8263    );
 8264
 8265    // ignore non-close brackets
 8266    cx.update_editor(|editor, window, cx| {
 8267        editor.handle_input("]", window, cx);
 8268        editor.move_left(&MoveLeft, window, cx);
 8269        editor.handle_input("]", window, cx);
 8270    });
 8271    cx.assert_editor_state(
 8272        &"
 8273            )}]ˇ]
 8274            )}]ˇ]
 8275            )}]ˇ]
 8276        "
 8277        .unindent(),
 8278    );
 8279}
 8280
 8281#[gpui::test]
 8282async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8283    init_test(cx, |_| {});
 8284
 8285    let mut cx = EditorTestContext::new(cx).await;
 8286
 8287    let html_language = Arc::new(
 8288        Language::new(
 8289            LanguageConfig {
 8290                name: "HTML".into(),
 8291                brackets: BracketPairConfig {
 8292                    pairs: vec![
 8293                        BracketPair {
 8294                            start: "<".into(),
 8295                            end: ">".into(),
 8296                            close: true,
 8297                            ..Default::default()
 8298                        },
 8299                        BracketPair {
 8300                            start: "{".into(),
 8301                            end: "}".into(),
 8302                            close: true,
 8303                            ..Default::default()
 8304                        },
 8305                        BracketPair {
 8306                            start: "(".into(),
 8307                            end: ")".into(),
 8308                            close: true,
 8309                            ..Default::default()
 8310                        },
 8311                    ],
 8312                    ..Default::default()
 8313                },
 8314                autoclose_before: "})]>".into(),
 8315                ..Default::default()
 8316            },
 8317            Some(tree_sitter_html::LANGUAGE.into()),
 8318        )
 8319        .with_injection_query(
 8320            r#"
 8321            (script_element
 8322                (raw_text) @injection.content
 8323                (#set! injection.language "javascript"))
 8324            "#,
 8325        )
 8326        .unwrap(),
 8327    );
 8328
 8329    let javascript_language = Arc::new(Language::new(
 8330        LanguageConfig {
 8331            name: "JavaScript".into(),
 8332            brackets: BracketPairConfig {
 8333                pairs: vec![
 8334                    BracketPair {
 8335                        start: "/*".into(),
 8336                        end: " */".into(),
 8337                        close: true,
 8338                        ..Default::default()
 8339                    },
 8340                    BracketPair {
 8341                        start: "{".into(),
 8342                        end: "}".into(),
 8343                        close: true,
 8344                        ..Default::default()
 8345                    },
 8346                    BracketPair {
 8347                        start: "(".into(),
 8348                        end: ")".into(),
 8349                        close: true,
 8350                        ..Default::default()
 8351                    },
 8352                ],
 8353                ..Default::default()
 8354            },
 8355            autoclose_before: "})]>".into(),
 8356            ..Default::default()
 8357        },
 8358        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8359    ));
 8360
 8361    cx.language_registry().add(html_language.clone());
 8362    cx.language_registry().add(javascript_language.clone());
 8363
 8364    cx.update_buffer(|buffer, cx| {
 8365        buffer.set_language(Some(html_language), cx);
 8366    });
 8367
 8368    cx.set_state(
 8369        &r#"
 8370            <body>ˇ
 8371                <script>
 8372                    var x = 1;ˇ
 8373                </script>
 8374            </body>ˇ
 8375        "#
 8376        .unindent(),
 8377    );
 8378
 8379    // Precondition: different languages are active at different locations.
 8380    cx.update_editor(|editor, window, cx| {
 8381        let snapshot = editor.snapshot(window, cx);
 8382        let cursors = editor.selections.ranges::<usize>(cx);
 8383        let languages = cursors
 8384            .iter()
 8385            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8386            .collect::<Vec<_>>();
 8387        assert_eq!(
 8388            languages,
 8389            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8390        );
 8391    });
 8392
 8393    // Angle brackets autoclose in HTML, but not JavaScript.
 8394    cx.update_editor(|editor, window, cx| {
 8395        editor.handle_input("<", window, cx);
 8396        editor.handle_input("a", window, cx);
 8397    });
 8398    cx.assert_editor_state(
 8399        &r#"
 8400            <body><aˇ>
 8401                <script>
 8402                    var x = 1;<aˇ
 8403                </script>
 8404            </body><aˇ>
 8405        "#
 8406        .unindent(),
 8407    );
 8408
 8409    // Curly braces and parens autoclose in both HTML and JavaScript.
 8410    cx.update_editor(|editor, window, cx| {
 8411        editor.handle_input(" b=", window, cx);
 8412        editor.handle_input("{", window, cx);
 8413        editor.handle_input("c", window, cx);
 8414        editor.handle_input("(", window, cx);
 8415    });
 8416    cx.assert_editor_state(
 8417        &r#"
 8418            <body><a b={c(ˇ)}>
 8419                <script>
 8420                    var x = 1;<a b={c(ˇ)}
 8421                </script>
 8422            </body><a b={c(ˇ)}>
 8423        "#
 8424        .unindent(),
 8425    );
 8426
 8427    // Brackets that were already autoclosed are skipped.
 8428    cx.update_editor(|editor, window, cx| {
 8429        editor.handle_input(")", window, cx);
 8430        editor.handle_input("d", window, cx);
 8431        editor.handle_input("}", window, cx);
 8432    });
 8433    cx.assert_editor_state(
 8434        &r#"
 8435            <body><a b={c()d}ˇ>
 8436                <script>
 8437                    var x = 1;<a b={c()d}ˇ
 8438                </script>
 8439            </body><a b={c()d}ˇ>
 8440        "#
 8441        .unindent(),
 8442    );
 8443    cx.update_editor(|editor, window, cx| {
 8444        editor.handle_input(">", window, cx);
 8445    });
 8446    cx.assert_editor_state(
 8447        &r#"
 8448            <body><a b={c()d}>ˇ
 8449                <script>
 8450                    var x = 1;<a b={c()d}>ˇ
 8451                </script>
 8452            </body><a b={c()d}>ˇ
 8453        "#
 8454        .unindent(),
 8455    );
 8456
 8457    // Reset
 8458    cx.set_state(
 8459        &r#"
 8460            <body>ˇ
 8461                <script>
 8462                    var x = 1;ˇ
 8463                </script>
 8464            </body>ˇ
 8465        "#
 8466        .unindent(),
 8467    );
 8468
 8469    cx.update_editor(|editor, window, cx| {
 8470        editor.handle_input("<", window, cx);
 8471    });
 8472    cx.assert_editor_state(
 8473        &r#"
 8474            <body><ˇ>
 8475                <script>
 8476                    var x = 1;<ˇ
 8477                </script>
 8478            </body><ˇ>
 8479        "#
 8480        .unindent(),
 8481    );
 8482
 8483    // When backspacing, the closing angle brackets are removed.
 8484    cx.update_editor(|editor, window, cx| {
 8485        editor.backspace(&Backspace, window, cx);
 8486    });
 8487    cx.assert_editor_state(
 8488        &r#"
 8489            <body>ˇ
 8490                <script>
 8491                    var x = 1;ˇ
 8492                </script>
 8493            </body>ˇ
 8494        "#
 8495        .unindent(),
 8496    );
 8497
 8498    // Block comments autoclose in JavaScript, but not HTML.
 8499    cx.update_editor(|editor, window, cx| {
 8500        editor.handle_input("/", window, cx);
 8501        editor.handle_input("*", window, cx);
 8502    });
 8503    cx.assert_editor_state(
 8504        &r#"
 8505            <body>/*ˇ
 8506                <script>
 8507                    var x = 1;/*ˇ */
 8508                </script>
 8509            </body>/*ˇ
 8510        "#
 8511        .unindent(),
 8512    );
 8513}
 8514
 8515#[gpui::test]
 8516async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8517    init_test(cx, |_| {});
 8518
 8519    let mut cx = EditorTestContext::new(cx).await;
 8520
 8521    let rust_language = Arc::new(
 8522        Language::new(
 8523            LanguageConfig {
 8524                name: "Rust".into(),
 8525                brackets: serde_json::from_value(json!([
 8526                    { "start": "{", "end": "}", "close": true, "newline": true },
 8527                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8528                ]))
 8529                .unwrap(),
 8530                autoclose_before: "})]>".into(),
 8531                ..Default::default()
 8532            },
 8533            Some(tree_sitter_rust::LANGUAGE.into()),
 8534        )
 8535        .with_override_query("(string_literal) @string")
 8536        .unwrap(),
 8537    );
 8538
 8539    cx.language_registry().add(rust_language.clone());
 8540    cx.update_buffer(|buffer, cx| {
 8541        buffer.set_language(Some(rust_language), cx);
 8542    });
 8543
 8544    cx.set_state(
 8545        &r#"
 8546            let x = ˇ
 8547        "#
 8548        .unindent(),
 8549    );
 8550
 8551    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8552    cx.update_editor(|editor, window, cx| {
 8553        editor.handle_input("\"", window, cx);
 8554    });
 8555    cx.assert_editor_state(
 8556        &r#"
 8557            let x = "ˇ"
 8558        "#
 8559        .unindent(),
 8560    );
 8561
 8562    // Inserting another quotation mark. The cursor moves across the existing
 8563    // automatically-inserted quotation mark.
 8564    cx.update_editor(|editor, window, cx| {
 8565        editor.handle_input("\"", window, cx);
 8566    });
 8567    cx.assert_editor_state(
 8568        &r#"
 8569            let x = ""ˇ
 8570        "#
 8571        .unindent(),
 8572    );
 8573
 8574    // Reset
 8575    cx.set_state(
 8576        &r#"
 8577            let x = ˇ
 8578        "#
 8579        .unindent(),
 8580    );
 8581
 8582    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8583    cx.update_editor(|editor, window, cx| {
 8584        editor.handle_input("\"", window, cx);
 8585        editor.handle_input(" ", window, cx);
 8586        editor.move_left(&Default::default(), window, cx);
 8587        editor.handle_input("\\", window, cx);
 8588        editor.handle_input("\"", window, cx);
 8589    });
 8590    cx.assert_editor_state(
 8591        &r#"
 8592            let x = "\"ˇ "
 8593        "#
 8594        .unindent(),
 8595    );
 8596
 8597    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8598    // mark. Nothing is inserted.
 8599    cx.update_editor(|editor, window, cx| {
 8600        editor.move_right(&Default::default(), window, cx);
 8601        editor.handle_input("\"", window, cx);
 8602    });
 8603    cx.assert_editor_state(
 8604        &r#"
 8605            let x = "\" "ˇ
 8606        "#
 8607        .unindent(),
 8608    );
 8609}
 8610
 8611#[gpui::test]
 8612async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8613    init_test(cx, |_| {});
 8614
 8615    let language = Arc::new(Language::new(
 8616        LanguageConfig {
 8617            brackets: BracketPairConfig {
 8618                pairs: vec![
 8619                    BracketPair {
 8620                        start: "{".to_string(),
 8621                        end: "}".to_string(),
 8622                        close: true,
 8623                        surround: true,
 8624                        newline: true,
 8625                    },
 8626                    BracketPair {
 8627                        start: "/* ".to_string(),
 8628                        end: "*/".to_string(),
 8629                        close: true,
 8630                        surround: true,
 8631                        ..Default::default()
 8632                    },
 8633                ],
 8634                ..Default::default()
 8635            },
 8636            ..Default::default()
 8637        },
 8638        Some(tree_sitter_rust::LANGUAGE.into()),
 8639    ));
 8640
 8641    let text = r#"
 8642        a
 8643        b
 8644        c
 8645    "#
 8646    .unindent();
 8647
 8648    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8649    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8650    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8651    editor
 8652        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8653        .await;
 8654
 8655    editor.update_in(cx, |editor, window, cx| {
 8656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8657            s.select_display_ranges([
 8658                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8659                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8660                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8661            ])
 8662        });
 8663
 8664        editor.handle_input("{", window, cx);
 8665        editor.handle_input("{", window, cx);
 8666        editor.handle_input("{", window, cx);
 8667        assert_eq!(
 8668            editor.text(cx),
 8669            "
 8670                {{{a}}}
 8671                {{{b}}}
 8672                {{{c}}}
 8673            "
 8674            .unindent()
 8675        );
 8676        assert_eq!(
 8677            editor.selections.display_ranges(cx),
 8678            [
 8679                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8680                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8681                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8682            ]
 8683        );
 8684
 8685        editor.undo(&Undo, window, cx);
 8686        editor.undo(&Undo, window, cx);
 8687        editor.undo(&Undo, window, cx);
 8688        assert_eq!(
 8689            editor.text(cx),
 8690            "
 8691                a
 8692                b
 8693                c
 8694            "
 8695            .unindent()
 8696        );
 8697        assert_eq!(
 8698            editor.selections.display_ranges(cx),
 8699            [
 8700                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8701                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8702                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8703            ]
 8704        );
 8705
 8706        // Ensure inserting the first character of a multi-byte bracket pair
 8707        // doesn't surround the selections with the bracket.
 8708        editor.handle_input("/", window, cx);
 8709        assert_eq!(
 8710            editor.text(cx),
 8711            "
 8712                /
 8713                /
 8714                /
 8715            "
 8716            .unindent()
 8717        );
 8718        assert_eq!(
 8719            editor.selections.display_ranges(cx),
 8720            [
 8721                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8722                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8723                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8724            ]
 8725        );
 8726
 8727        editor.undo(&Undo, window, cx);
 8728        assert_eq!(
 8729            editor.text(cx),
 8730            "
 8731                a
 8732                b
 8733                c
 8734            "
 8735            .unindent()
 8736        );
 8737        assert_eq!(
 8738            editor.selections.display_ranges(cx),
 8739            [
 8740                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8741                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8742                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8743            ]
 8744        );
 8745
 8746        // Ensure inserting the last character of a multi-byte bracket pair
 8747        // doesn't surround the selections with the bracket.
 8748        editor.handle_input("*", window, cx);
 8749        assert_eq!(
 8750            editor.text(cx),
 8751            "
 8752                *
 8753                *
 8754                *
 8755            "
 8756            .unindent()
 8757        );
 8758        assert_eq!(
 8759            editor.selections.display_ranges(cx),
 8760            [
 8761                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8762                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8763                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8764            ]
 8765        );
 8766    });
 8767}
 8768
 8769#[gpui::test]
 8770async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8771    init_test(cx, |_| {});
 8772
 8773    let language = Arc::new(Language::new(
 8774        LanguageConfig {
 8775            brackets: BracketPairConfig {
 8776                pairs: vec![BracketPair {
 8777                    start: "{".to_string(),
 8778                    end: "}".to_string(),
 8779                    close: true,
 8780                    surround: true,
 8781                    newline: true,
 8782                }],
 8783                ..Default::default()
 8784            },
 8785            autoclose_before: "}".to_string(),
 8786            ..Default::default()
 8787        },
 8788        Some(tree_sitter_rust::LANGUAGE.into()),
 8789    ));
 8790
 8791    let text = r#"
 8792        a
 8793        b
 8794        c
 8795    "#
 8796    .unindent();
 8797
 8798    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8799    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8800    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8801    editor
 8802        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8803        .await;
 8804
 8805    editor.update_in(cx, |editor, window, cx| {
 8806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8807            s.select_ranges([
 8808                Point::new(0, 1)..Point::new(0, 1),
 8809                Point::new(1, 1)..Point::new(1, 1),
 8810                Point::new(2, 1)..Point::new(2, 1),
 8811            ])
 8812        });
 8813
 8814        editor.handle_input("{", window, cx);
 8815        editor.handle_input("{", window, cx);
 8816        editor.handle_input("_", window, cx);
 8817        assert_eq!(
 8818            editor.text(cx),
 8819            "
 8820                a{{_}}
 8821                b{{_}}
 8822                c{{_}}
 8823            "
 8824            .unindent()
 8825        );
 8826        assert_eq!(
 8827            editor.selections.ranges::<Point>(cx),
 8828            [
 8829                Point::new(0, 4)..Point::new(0, 4),
 8830                Point::new(1, 4)..Point::new(1, 4),
 8831                Point::new(2, 4)..Point::new(2, 4)
 8832            ]
 8833        );
 8834
 8835        editor.backspace(&Default::default(), window, cx);
 8836        editor.backspace(&Default::default(), window, cx);
 8837        assert_eq!(
 8838            editor.text(cx),
 8839            "
 8840                a{}
 8841                b{}
 8842                c{}
 8843            "
 8844            .unindent()
 8845        );
 8846        assert_eq!(
 8847            editor.selections.ranges::<Point>(cx),
 8848            [
 8849                Point::new(0, 2)..Point::new(0, 2),
 8850                Point::new(1, 2)..Point::new(1, 2),
 8851                Point::new(2, 2)..Point::new(2, 2)
 8852            ]
 8853        );
 8854
 8855        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8856        assert_eq!(
 8857            editor.text(cx),
 8858            "
 8859                a
 8860                b
 8861                c
 8862            "
 8863            .unindent()
 8864        );
 8865        assert_eq!(
 8866            editor.selections.ranges::<Point>(cx),
 8867            [
 8868                Point::new(0, 1)..Point::new(0, 1),
 8869                Point::new(1, 1)..Point::new(1, 1),
 8870                Point::new(2, 1)..Point::new(2, 1)
 8871            ]
 8872        );
 8873    });
 8874}
 8875
 8876#[gpui::test]
 8877async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8878    init_test(cx, |settings| {
 8879        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8880    });
 8881
 8882    let mut cx = EditorTestContext::new(cx).await;
 8883
 8884    let language = Arc::new(Language::new(
 8885        LanguageConfig {
 8886            brackets: BracketPairConfig {
 8887                pairs: vec![
 8888                    BracketPair {
 8889                        start: "{".to_string(),
 8890                        end: "}".to_string(),
 8891                        close: true,
 8892                        surround: true,
 8893                        newline: true,
 8894                    },
 8895                    BracketPair {
 8896                        start: "(".to_string(),
 8897                        end: ")".to_string(),
 8898                        close: true,
 8899                        surround: true,
 8900                        newline: true,
 8901                    },
 8902                    BracketPair {
 8903                        start: "[".to_string(),
 8904                        end: "]".to_string(),
 8905                        close: false,
 8906                        surround: true,
 8907                        newline: true,
 8908                    },
 8909                ],
 8910                ..Default::default()
 8911            },
 8912            autoclose_before: "})]".to_string(),
 8913            ..Default::default()
 8914        },
 8915        Some(tree_sitter_rust::LANGUAGE.into()),
 8916    ));
 8917
 8918    cx.language_registry().add(language.clone());
 8919    cx.update_buffer(|buffer, cx| {
 8920        buffer.set_language(Some(language), cx);
 8921    });
 8922
 8923    cx.set_state(
 8924        &"
 8925            {(ˇ)}
 8926            [[ˇ]]
 8927            {(ˇ)}
 8928        "
 8929        .unindent(),
 8930    );
 8931
 8932    cx.update_editor(|editor, window, cx| {
 8933        editor.backspace(&Default::default(), window, cx);
 8934        editor.backspace(&Default::default(), window, cx);
 8935    });
 8936
 8937    cx.assert_editor_state(
 8938        &"
 8939            ˇ
 8940            ˇ]]
 8941            ˇ
 8942        "
 8943        .unindent(),
 8944    );
 8945
 8946    cx.update_editor(|editor, window, cx| {
 8947        editor.handle_input("{", window, cx);
 8948        editor.handle_input("{", window, cx);
 8949        editor.move_right(&MoveRight, window, cx);
 8950        editor.move_right(&MoveRight, window, cx);
 8951        editor.move_left(&MoveLeft, window, cx);
 8952        editor.move_left(&MoveLeft, window, cx);
 8953        editor.backspace(&Default::default(), window, cx);
 8954    });
 8955
 8956    cx.assert_editor_state(
 8957        &"
 8958            {ˇ}
 8959            {ˇ}]]
 8960            {ˇ}
 8961        "
 8962        .unindent(),
 8963    );
 8964
 8965    cx.update_editor(|editor, window, cx| {
 8966        editor.backspace(&Default::default(), window, cx);
 8967    });
 8968
 8969    cx.assert_editor_state(
 8970        &"
 8971            ˇ
 8972            ˇ]]
 8973            ˇ
 8974        "
 8975        .unindent(),
 8976    );
 8977}
 8978
 8979#[gpui::test]
 8980async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8981    init_test(cx, |_| {});
 8982
 8983    let language = Arc::new(Language::new(
 8984        LanguageConfig::default(),
 8985        Some(tree_sitter_rust::LANGUAGE.into()),
 8986    ));
 8987
 8988    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8989    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8990    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8991    editor
 8992        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8993        .await;
 8994
 8995    editor.update_in(cx, |editor, window, cx| {
 8996        editor.set_auto_replace_emoji_shortcode(true);
 8997
 8998        editor.handle_input("Hello ", window, cx);
 8999        editor.handle_input(":wave", window, cx);
 9000        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9001
 9002        editor.handle_input(":", window, cx);
 9003        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9004
 9005        editor.handle_input(" :smile", window, cx);
 9006        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9007
 9008        editor.handle_input(":", window, cx);
 9009        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9010
 9011        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9012        editor.handle_input(":wave", window, cx);
 9013        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9014
 9015        editor.handle_input(":", window, cx);
 9016        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9017
 9018        editor.handle_input(":1", window, cx);
 9019        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9020
 9021        editor.handle_input(":", window, cx);
 9022        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9023
 9024        // Ensure shortcode does not get replaced when it is part of a word
 9025        editor.handle_input(" Test:wave", window, cx);
 9026        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9027
 9028        editor.handle_input(":", window, cx);
 9029        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9030
 9031        editor.set_auto_replace_emoji_shortcode(false);
 9032
 9033        // Ensure shortcode does not get replaced when auto replace is off
 9034        editor.handle_input(" :wave", window, cx);
 9035        assert_eq!(
 9036            editor.text(cx),
 9037            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9038        );
 9039
 9040        editor.handle_input(":", window, cx);
 9041        assert_eq!(
 9042            editor.text(cx),
 9043            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9044        );
 9045    });
 9046}
 9047
 9048#[gpui::test]
 9049async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9050    init_test(cx, |_| {});
 9051
 9052    let (text, insertion_ranges) = marked_text_ranges(
 9053        indoc! {"
 9054            ˇ
 9055        "},
 9056        false,
 9057    );
 9058
 9059    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9061
 9062    _ = editor.update_in(cx, |editor, window, cx| {
 9063        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9064
 9065        editor
 9066            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9067            .unwrap();
 9068
 9069        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9070            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9071            assert_eq!(editor.text(cx), expected_text);
 9072            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9073        }
 9074
 9075        assert(
 9076            editor,
 9077            cx,
 9078            indoc! {"
 9079            type «» =•
 9080            "},
 9081        );
 9082
 9083        assert!(editor.context_menu_visible(), "There should be a matches");
 9084    });
 9085}
 9086
 9087#[gpui::test]
 9088async fn test_snippets(cx: &mut TestAppContext) {
 9089    init_test(cx, |_| {});
 9090
 9091    let mut cx = EditorTestContext::new(cx).await;
 9092
 9093    cx.set_state(indoc! {"
 9094        a.ˇ b
 9095        a.ˇ b
 9096        a.ˇ b
 9097    "});
 9098
 9099    cx.update_editor(|editor, window, cx| {
 9100        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9101        let insertion_ranges = editor
 9102            .selections
 9103            .all(cx)
 9104            .iter()
 9105            .map(|s| s.range().clone())
 9106            .collect::<Vec<_>>();
 9107        editor
 9108            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9109            .unwrap();
 9110    });
 9111
 9112    cx.assert_editor_state(indoc! {"
 9113        a.f(«oneˇ», two, «threeˇ») b
 9114        a.f(«oneˇ», two, «threeˇ») b
 9115        a.f(«oneˇ», two, «threeˇ») b
 9116    "});
 9117
 9118    // Can't move earlier than the first tab stop
 9119    cx.update_editor(|editor, window, cx| {
 9120        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9121    });
 9122    cx.assert_editor_state(indoc! {"
 9123        a.f(«oneˇ», two, «threeˇ») b
 9124        a.f(«oneˇ», two, «threeˇ») b
 9125        a.f(«oneˇ», two, «threeˇ») b
 9126    "});
 9127
 9128    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9129    cx.assert_editor_state(indoc! {"
 9130        a.f(one, «twoˇ», three) b
 9131        a.f(one, «twoˇ», three) b
 9132        a.f(one, «twoˇ», three) b
 9133    "});
 9134
 9135    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9136    cx.assert_editor_state(indoc! {"
 9137        a.f(«oneˇ», two, «threeˇ») b
 9138        a.f(«oneˇ», two, «threeˇ») b
 9139        a.f(«oneˇ», two, «threeˇ») b
 9140    "});
 9141
 9142    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9143    cx.assert_editor_state(indoc! {"
 9144        a.f(one, «twoˇ», three) b
 9145        a.f(one, «twoˇ», three) b
 9146        a.f(one, «twoˇ», three) b
 9147    "});
 9148    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9149    cx.assert_editor_state(indoc! {"
 9150        a.f(one, two, three)ˇ b
 9151        a.f(one, two, three)ˇ b
 9152        a.f(one, two, three)ˇ b
 9153    "});
 9154
 9155    // As soon as the last tab stop is reached, snippet state is gone
 9156    cx.update_editor(|editor, window, cx| {
 9157        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9158    });
 9159    cx.assert_editor_state(indoc! {"
 9160        a.f(one, two, three)ˇ b
 9161        a.f(one, two, three)ˇ b
 9162        a.f(one, two, three)ˇ b
 9163    "});
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let mut cx = EditorTestContext::new(cx).await;
 9171
 9172    cx.update_editor(|editor, window, cx| {
 9173        let snippet = Snippet::parse(indoc! {"
 9174            /*
 9175             * Multiline comment with leading indentation
 9176             *
 9177             * $1
 9178             */
 9179            $0"})
 9180        .unwrap();
 9181        let insertion_ranges = editor
 9182            .selections
 9183            .all(cx)
 9184            .iter()
 9185            .map(|s| s.range().clone())
 9186            .collect::<Vec<_>>();
 9187        editor
 9188            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9189            .unwrap();
 9190    });
 9191
 9192    cx.assert_editor_state(indoc! {"
 9193        /*
 9194         * Multiline comment with leading indentation
 9195         *
 9196         * ˇ
 9197         */
 9198    "});
 9199
 9200    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9201    cx.assert_editor_state(indoc! {"
 9202        /*
 9203         * Multiline comment with leading indentation
 9204         *
 9205         *•
 9206         */
 9207        ˇ"});
 9208}
 9209
 9210#[gpui::test]
 9211async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9212    init_test(cx, |_| {});
 9213
 9214    let fs = FakeFs::new(cx.executor());
 9215    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9216
 9217    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9218
 9219    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9220    language_registry.add(rust_lang());
 9221    let mut fake_servers = language_registry.register_fake_lsp(
 9222        "Rust",
 9223        FakeLspAdapter {
 9224            capabilities: lsp::ServerCapabilities {
 9225                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9226                ..Default::default()
 9227            },
 9228            ..Default::default()
 9229        },
 9230    );
 9231
 9232    let buffer = project
 9233        .update(cx, |project, cx| {
 9234            project.open_local_buffer(path!("/file.rs"), cx)
 9235        })
 9236        .await
 9237        .unwrap();
 9238
 9239    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9240    let (editor, cx) = cx.add_window_view(|window, cx| {
 9241        build_editor_with_project(project.clone(), buffer, window, cx)
 9242    });
 9243    editor.update_in(cx, |editor, window, cx| {
 9244        editor.set_text("one\ntwo\nthree\n", window, cx)
 9245    });
 9246    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9247
 9248    cx.executor().start_waiting();
 9249    let fake_server = fake_servers.next().await.unwrap();
 9250
 9251    {
 9252        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9253            move |params, _| async move {
 9254                assert_eq!(
 9255                    params.text_document.uri,
 9256                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9257                );
 9258                assert_eq!(params.options.tab_size, 4);
 9259                Ok(Some(vec![lsp::TextEdit::new(
 9260                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9261                    ", ".to_string(),
 9262                )]))
 9263            },
 9264        );
 9265        let save = editor
 9266            .update_in(cx, |editor, window, cx| {
 9267                editor.save(
 9268                    SaveOptions {
 9269                        format: true,
 9270                        autosave: false,
 9271                    },
 9272                    project.clone(),
 9273                    window,
 9274                    cx,
 9275                )
 9276            })
 9277            .unwrap();
 9278        cx.executor().start_waiting();
 9279        save.await;
 9280
 9281        assert_eq!(
 9282            editor.update(cx, |editor, cx| editor.text(cx)),
 9283            "one, two\nthree\n"
 9284        );
 9285        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9286    }
 9287
 9288    {
 9289        editor.update_in(cx, |editor, window, cx| {
 9290            editor.set_text("one\ntwo\nthree\n", window, cx)
 9291        });
 9292        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9293
 9294        // Ensure we can still save even if formatting hangs.
 9295        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9296            move |params, _| async move {
 9297                assert_eq!(
 9298                    params.text_document.uri,
 9299                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9300                );
 9301                futures::future::pending::<()>().await;
 9302                unreachable!()
 9303            },
 9304        );
 9305        let save = editor
 9306            .update_in(cx, |editor, window, cx| {
 9307                editor.save(
 9308                    SaveOptions {
 9309                        format: true,
 9310                        autosave: false,
 9311                    },
 9312                    project.clone(),
 9313                    window,
 9314                    cx,
 9315                )
 9316            })
 9317            .unwrap();
 9318        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9319        cx.executor().start_waiting();
 9320        save.await;
 9321        assert_eq!(
 9322            editor.update(cx, |editor, cx| editor.text(cx)),
 9323            "one\ntwo\nthree\n"
 9324        );
 9325    }
 9326
 9327    // Set rust language override and assert overridden tabsize is sent to language server
 9328    update_test_language_settings(cx, |settings| {
 9329        settings.languages.insert(
 9330            "Rust".into(),
 9331            LanguageSettingsContent {
 9332                tab_size: NonZeroU32::new(8),
 9333                ..Default::default()
 9334            },
 9335        );
 9336    });
 9337
 9338    {
 9339        editor.update_in(cx, |editor, window, cx| {
 9340            editor.set_text("somehting_new\n", window, cx)
 9341        });
 9342        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9343        let _formatting_request_signal = fake_server
 9344            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9345                assert_eq!(
 9346                    params.text_document.uri,
 9347                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9348                );
 9349                assert_eq!(params.options.tab_size, 8);
 9350                Ok(Some(vec![]))
 9351            });
 9352        let save = editor
 9353            .update_in(cx, |editor, window, cx| {
 9354                editor.save(
 9355                    SaveOptions {
 9356                        format: true,
 9357                        autosave: false,
 9358                    },
 9359                    project.clone(),
 9360                    window,
 9361                    cx,
 9362                )
 9363            })
 9364            .unwrap();
 9365        cx.executor().start_waiting();
 9366        save.await;
 9367    }
 9368}
 9369
 9370#[gpui::test]
 9371async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9372    init_test(cx, |_| {});
 9373
 9374    let cols = 4;
 9375    let rows = 10;
 9376    let sample_text_1 = sample_text(rows, cols, 'a');
 9377    assert_eq!(
 9378        sample_text_1,
 9379        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9380    );
 9381    let sample_text_2 = sample_text(rows, cols, 'l');
 9382    assert_eq!(
 9383        sample_text_2,
 9384        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9385    );
 9386    let sample_text_3 = sample_text(rows, cols, 'v');
 9387    assert_eq!(
 9388        sample_text_3,
 9389        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9390    );
 9391
 9392    let fs = FakeFs::new(cx.executor());
 9393    fs.insert_tree(
 9394        path!("/a"),
 9395        json!({
 9396            "main.rs": sample_text_1,
 9397            "other.rs": sample_text_2,
 9398            "lib.rs": sample_text_3,
 9399        }),
 9400    )
 9401    .await;
 9402
 9403    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9404    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9405    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9406
 9407    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9408    language_registry.add(rust_lang());
 9409    let mut fake_servers = language_registry.register_fake_lsp(
 9410        "Rust",
 9411        FakeLspAdapter {
 9412            capabilities: lsp::ServerCapabilities {
 9413                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9414                ..Default::default()
 9415            },
 9416            ..Default::default()
 9417        },
 9418    );
 9419
 9420    let worktree = project.update(cx, |project, cx| {
 9421        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9422        assert_eq!(worktrees.len(), 1);
 9423        worktrees.pop().unwrap()
 9424    });
 9425    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9426
 9427    let buffer_1 = project
 9428        .update(cx, |project, cx| {
 9429            project.open_buffer((worktree_id, "main.rs"), cx)
 9430        })
 9431        .await
 9432        .unwrap();
 9433    let buffer_2 = project
 9434        .update(cx, |project, cx| {
 9435            project.open_buffer((worktree_id, "other.rs"), cx)
 9436        })
 9437        .await
 9438        .unwrap();
 9439    let buffer_3 = project
 9440        .update(cx, |project, cx| {
 9441            project.open_buffer((worktree_id, "lib.rs"), cx)
 9442        })
 9443        .await
 9444        .unwrap();
 9445
 9446    let multi_buffer = cx.new(|cx| {
 9447        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9448        multi_buffer.push_excerpts(
 9449            buffer_1.clone(),
 9450            [
 9451                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9452                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9453                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9454            ],
 9455            cx,
 9456        );
 9457        multi_buffer.push_excerpts(
 9458            buffer_2.clone(),
 9459            [
 9460                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9461                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9462                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9463            ],
 9464            cx,
 9465        );
 9466        multi_buffer.push_excerpts(
 9467            buffer_3.clone(),
 9468            [
 9469                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9470                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9471                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9472            ],
 9473            cx,
 9474        );
 9475        multi_buffer
 9476    });
 9477    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9478        Editor::new(
 9479            EditorMode::full(),
 9480            multi_buffer,
 9481            Some(project.clone()),
 9482            window,
 9483            cx,
 9484        )
 9485    });
 9486
 9487    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9488        editor.change_selections(
 9489            SelectionEffects::scroll(Autoscroll::Next),
 9490            window,
 9491            cx,
 9492            |s| s.select_ranges(Some(1..2)),
 9493        );
 9494        editor.insert("|one|two|three|", window, cx);
 9495    });
 9496    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9497    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9498        editor.change_selections(
 9499            SelectionEffects::scroll(Autoscroll::Next),
 9500            window,
 9501            cx,
 9502            |s| s.select_ranges(Some(60..70)),
 9503        );
 9504        editor.insert("|four|five|six|", window, cx);
 9505    });
 9506    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9507
 9508    // First two buffers should be edited, but not the third one.
 9509    assert_eq!(
 9510        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9511        "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}",
 9512    );
 9513    buffer_1.update(cx, |buffer, _| {
 9514        assert!(buffer.is_dirty());
 9515        assert_eq!(
 9516            buffer.text(),
 9517            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9518        )
 9519    });
 9520    buffer_2.update(cx, |buffer, _| {
 9521        assert!(buffer.is_dirty());
 9522        assert_eq!(
 9523            buffer.text(),
 9524            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9525        )
 9526    });
 9527    buffer_3.update(cx, |buffer, _| {
 9528        assert!(!buffer.is_dirty());
 9529        assert_eq!(buffer.text(), sample_text_3,)
 9530    });
 9531    cx.executor().run_until_parked();
 9532
 9533    cx.executor().start_waiting();
 9534    let save = multi_buffer_editor
 9535        .update_in(cx, |editor, window, cx| {
 9536            editor.save(
 9537                SaveOptions {
 9538                    format: true,
 9539                    autosave: false,
 9540                },
 9541                project.clone(),
 9542                window,
 9543                cx,
 9544            )
 9545        })
 9546        .unwrap();
 9547
 9548    let fake_server = fake_servers.next().await.unwrap();
 9549    fake_server
 9550        .server
 9551        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9552            Ok(Some(vec![lsp::TextEdit::new(
 9553                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9554                format!("[{} formatted]", params.text_document.uri),
 9555            )]))
 9556        })
 9557        .detach();
 9558    save.await;
 9559
 9560    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9561    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9562    assert_eq!(
 9563        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9564        uri!(
 9565            "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}"
 9566        ),
 9567    );
 9568    buffer_1.update(cx, |buffer, _| {
 9569        assert!(!buffer.is_dirty());
 9570        assert_eq!(
 9571            buffer.text(),
 9572            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9573        )
 9574    });
 9575    buffer_2.update(cx, |buffer, _| {
 9576        assert!(!buffer.is_dirty());
 9577        assert_eq!(
 9578            buffer.text(),
 9579            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9580        )
 9581    });
 9582    buffer_3.update(cx, |buffer, _| {
 9583        assert!(!buffer.is_dirty());
 9584        assert_eq!(buffer.text(), sample_text_3,)
 9585    });
 9586}
 9587
 9588#[gpui::test]
 9589async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9590    init_test(cx, |_| {});
 9591
 9592    let fs = FakeFs::new(cx.executor());
 9593    fs.insert_tree(
 9594        path!("/dir"),
 9595        json!({
 9596            "file1.rs": "fn main() { println!(\"hello\"); }",
 9597            "file2.rs": "fn test() { println!(\"test\"); }",
 9598            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9599        }),
 9600    )
 9601    .await;
 9602
 9603    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9604    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9605    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9606
 9607    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9608    language_registry.add(rust_lang());
 9609
 9610    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9611    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9612
 9613    // Open three buffers
 9614    let buffer_1 = project
 9615        .update(cx, |project, cx| {
 9616            project.open_buffer((worktree_id, "file1.rs"), cx)
 9617        })
 9618        .await
 9619        .unwrap();
 9620    let buffer_2 = project
 9621        .update(cx, |project, cx| {
 9622            project.open_buffer((worktree_id, "file2.rs"), cx)
 9623        })
 9624        .await
 9625        .unwrap();
 9626    let buffer_3 = project
 9627        .update(cx, |project, cx| {
 9628            project.open_buffer((worktree_id, "file3.rs"), cx)
 9629        })
 9630        .await
 9631        .unwrap();
 9632
 9633    // Create a multi-buffer with all three buffers
 9634    let multi_buffer = cx.new(|cx| {
 9635        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9636        multi_buffer.push_excerpts(
 9637            buffer_1.clone(),
 9638            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9639            cx,
 9640        );
 9641        multi_buffer.push_excerpts(
 9642            buffer_2.clone(),
 9643            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9644            cx,
 9645        );
 9646        multi_buffer.push_excerpts(
 9647            buffer_3.clone(),
 9648            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9649            cx,
 9650        );
 9651        multi_buffer
 9652    });
 9653
 9654    let editor = cx.new_window_entity(|window, cx| {
 9655        Editor::new(
 9656            EditorMode::full(),
 9657            multi_buffer,
 9658            Some(project.clone()),
 9659            window,
 9660            cx,
 9661        )
 9662    });
 9663
 9664    // Edit only the first buffer
 9665    editor.update_in(cx, |editor, window, cx| {
 9666        editor.change_selections(
 9667            SelectionEffects::scroll(Autoscroll::Next),
 9668            window,
 9669            cx,
 9670            |s| s.select_ranges(Some(10..10)),
 9671        );
 9672        editor.insert("// edited", window, cx);
 9673    });
 9674
 9675    // Verify that only buffer 1 is dirty
 9676    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9677    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9678    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9679
 9680    // Get write counts after file creation (files were created with initial content)
 9681    // We expect each file to have been written once during creation
 9682    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9683    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9684    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9685
 9686    // Perform autosave
 9687    let save_task = editor.update_in(cx, |editor, window, cx| {
 9688        editor.save(
 9689            SaveOptions {
 9690                format: true,
 9691                autosave: true,
 9692            },
 9693            project.clone(),
 9694            window,
 9695            cx,
 9696        )
 9697    });
 9698    save_task.await.unwrap();
 9699
 9700    // Only the dirty buffer should have been saved
 9701    assert_eq!(
 9702        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9703        1,
 9704        "Buffer 1 was dirty, so it should have been written once during autosave"
 9705    );
 9706    assert_eq!(
 9707        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9708        0,
 9709        "Buffer 2 was clean, so it should not have been written during autosave"
 9710    );
 9711    assert_eq!(
 9712        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9713        0,
 9714        "Buffer 3 was clean, so it should not have been written during autosave"
 9715    );
 9716
 9717    // Verify buffer states after autosave
 9718    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9719    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9720    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9721
 9722    // Now perform a manual save (format = true)
 9723    let save_task = editor.update_in(cx, |editor, window, cx| {
 9724        editor.save(
 9725            SaveOptions {
 9726                format: true,
 9727                autosave: false,
 9728            },
 9729            project.clone(),
 9730            window,
 9731            cx,
 9732        )
 9733    });
 9734    save_task.await.unwrap();
 9735
 9736    // During manual save, clean buffers don't get written to disk
 9737    // They just get did_save called for language server notifications
 9738    assert_eq!(
 9739        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9740        1,
 9741        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9742    );
 9743    assert_eq!(
 9744        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9745        0,
 9746        "Buffer 2 should not have been written at all"
 9747    );
 9748    assert_eq!(
 9749        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9750        0,
 9751        "Buffer 3 should not have been written at all"
 9752    );
 9753}
 9754
 9755#[gpui::test]
 9756async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9757    init_test(cx, |_| {});
 9758
 9759    let fs = FakeFs::new(cx.executor());
 9760    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9761
 9762    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9763
 9764    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9765    language_registry.add(rust_lang());
 9766    let mut fake_servers = language_registry.register_fake_lsp(
 9767        "Rust",
 9768        FakeLspAdapter {
 9769            capabilities: lsp::ServerCapabilities {
 9770                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9771                ..Default::default()
 9772            },
 9773            ..Default::default()
 9774        },
 9775    );
 9776
 9777    let buffer = project
 9778        .update(cx, |project, cx| {
 9779            project.open_local_buffer(path!("/file.rs"), cx)
 9780        })
 9781        .await
 9782        .unwrap();
 9783
 9784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9785    let (editor, cx) = cx.add_window_view(|window, cx| {
 9786        build_editor_with_project(project.clone(), buffer, window, cx)
 9787    });
 9788    editor.update_in(cx, |editor, window, cx| {
 9789        editor.set_text("one\ntwo\nthree\n", window, cx)
 9790    });
 9791    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9792
 9793    cx.executor().start_waiting();
 9794    let fake_server = fake_servers.next().await.unwrap();
 9795
 9796    let save = editor
 9797        .update_in(cx, |editor, window, cx| {
 9798            editor.save(
 9799                SaveOptions {
 9800                    format: true,
 9801                    autosave: false,
 9802                },
 9803                project.clone(),
 9804                window,
 9805                cx,
 9806            )
 9807        })
 9808        .unwrap();
 9809    fake_server
 9810        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9811            assert_eq!(
 9812                params.text_document.uri,
 9813                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9814            );
 9815            assert_eq!(params.options.tab_size, 4);
 9816            Ok(Some(vec![lsp::TextEdit::new(
 9817                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9818                ", ".to_string(),
 9819            )]))
 9820        })
 9821        .next()
 9822        .await;
 9823    cx.executor().start_waiting();
 9824    save.await;
 9825    assert_eq!(
 9826        editor.update(cx, |editor, cx| editor.text(cx)),
 9827        "one, two\nthree\n"
 9828    );
 9829    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9830
 9831    editor.update_in(cx, |editor, window, cx| {
 9832        editor.set_text("one\ntwo\nthree\n", window, cx)
 9833    });
 9834    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9835
 9836    // Ensure we can still save even if formatting hangs.
 9837    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9838        move |params, _| async move {
 9839            assert_eq!(
 9840                params.text_document.uri,
 9841                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9842            );
 9843            futures::future::pending::<()>().await;
 9844            unreachable!()
 9845        },
 9846    );
 9847    let save = editor
 9848        .update_in(cx, |editor, window, cx| {
 9849            editor.save(
 9850                SaveOptions {
 9851                    format: true,
 9852                    autosave: false,
 9853                },
 9854                project.clone(),
 9855                window,
 9856                cx,
 9857            )
 9858        })
 9859        .unwrap();
 9860    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9861    cx.executor().start_waiting();
 9862    save.await;
 9863    assert_eq!(
 9864        editor.update(cx, |editor, cx| editor.text(cx)),
 9865        "one\ntwo\nthree\n"
 9866    );
 9867    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9868
 9869    // For non-dirty buffer, no formatting request should be sent
 9870    let save = editor
 9871        .update_in(cx, |editor, window, cx| {
 9872            editor.save(
 9873                SaveOptions {
 9874                    format: false,
 9875                    autosave: false,
 9876                },
 9877                project.clone(),
 9878                window,
 9879                cx,
 9880            )
 9881        })
 9882        .unwrap();
 9883    let _pending_format_request = fake_server
 9884        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9885            panic!("Should not be invoked");
 9886        })
 9887        .next();
 9888    cx.executor().start_waiting();
 9889    save.await;
 9890
 9891    // Set Rust language override and assert overridden tabsize is sent to language server
 9892    update_test_language_settings(cx, |settings| {
 9893        settings.languages.insert(
 9894            "Rust".into(),
 9895            LanguageSettingsContent {
 9896                tab_size: NonZeroU32::new(8),
 9897                ..Default::default()
 9898            },
 9899        );
 9900    });
 9901
 9902    editor.update_in(cx, |editor, window, cx| {
 9903        editor.set_text("somehting_new\n", window, cx)
 9904    });
 9905    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9906    let save = editor
 9907        .update_in(cx, |editor, window, cx| {
 9908            editor.save(
 9909                SaveOptions {
 9910                    format: true,
 9911                    autosave: false,
 9912                },
 9913                project.clone(),
 9914                window,
 9915                cx,
 9916            )
 9917        })
 9918        .unwrap();
 9919    fake_server
 9920        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9921            assert_eq!(
 9922                params.text_document.uri,
 9923                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9924            );
 9925            assert_eq!(params.options.tab_size, 8);
 9926            Ok(Some(Vec::new()))
 9927        })
 9928        .next()
 9929        .await;
 9930    save.await;
 9931}
 9932
 9933#[gpui::test]
 9934async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9935    init_test(cx, |settings| {
 9936        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9937            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9938        ))
 9939    });
 9940
 9941    let fs = FakeFs::new(cx.executor());
 9942    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9943
 9944    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9945
 9946    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9947    language_registry.add(Arc::new(Language::new(
 9948        LanguageConfig {
 9949            name: "Rust".into(),
 9950            matcher: LanguageMatcher {
 9951                path_suffixes: vec!["rs".to_string()],
 9952                ..Default::default()
 9953            },
 9954            ..LanguageConfig::default()
 9955        },
 9956        Some(tree_sitter_rust::LANGUAGE.into()),
 9957    )));
 9958    update_test_language_settings(cx, |settings| {
 9959        // Enable Prettier formatting for the same buffer, and ensure
 9960        // LSP is called instead of Prettier.
 9961        settings.defaults.prettier = Some(PrettierSettings {
 9962            allowed: true,
 9963            ..PrettierSettings::default()
 9964        });
 9965    });
 9966    let mut fake_servers = language_registry.register_fake_lsp(
 9967        "Rust",
 9968        FakeLspAdapter {
 9969            capabilities: lsp::ServerCapabilities {
 9970                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9971                ..Default::default()
 9972            },
 9973            ..Default::default()
 9974        },
 9975    );
 9976
 9977    let buffer = project
 9978        .update(cx, |project, cx| {
 9979            project.open_local_buffer(path!("/file.rs"), cx)
 9980        })
 9981        .await
 9982        .unwrap();
 9983
 9984    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9985    let (editor, cx) = cx.add_window_view(|window, cx| {
 9986        build_editor_with_project(project.clone(), buffer, window, cx)
 9987    });
 9988    editor.update_in(cx, |editor, window, cx| {
 9989        editor.set_text("one\ntwo\nthree\n", window, cx)
 9990    });
 9991
 9992    cx.executor().start_waiting();
 9993    let fake_server = fake_servers.next().await.unwrap();
 9994
 9995    let format = editor
 9996        .update_in(cx, |editor, window, cx| {
 9997            editor.perform_format(
 9998                project.clone(),
 9999                FormatTrigger::Manual,
10000                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10001                window,
10002                cx,
10003            )
10004        })
10005        .unwrap();
10006    fake_server
10007        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10008            assert_eq!(
10009                params.text_document.uri,
10010                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10011            );
10012            assert_eq!(params.options.tab_size, 4);
10013            Ok(Some(vec![lsp::TextEdit::new(
10014                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10015                ", ".to_string(),
10016            )]))
10017        })
10018        .next()
10019        .await;
10020    cx.executor().start_waiting();
10021    format.await;
10022    assert_eq!(
10023        editor.update(cx, |editor, cx| editor.text(cx)),
10024        "one, two\nthree\n"
10025    );
10026
10027    editor.update_in(cx, |editor, window, cx| {
10028        editor.set_text("one\ntwo\nthree\n", window, cx)
10029    });
10030    // Ensure we don't lock if formatting hangs.
10031    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10032        move |params, _| async move {
10033            assert_eq!(
10034                params.text_document.uri,
10035                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10036            );
10037            futures::future::pending::<()>().await;
10038            unreachable!()
10039        },
10040    );
10041    let format = editor
10042        .update_in(cx, |editor, window, cx| {
10043            editor.perform_format(
10044                project,
10045                FormatTrigger::Manual,
10046                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10047                window,
10048                cx,
10049            )
10050        })
10051        .unwrap();
10052    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10053    cx.executor().start_waiting();
10054    format.await;
10055    assert_eq!(
10056        editor.update(cx, |editor, cx| editor.text(cx)),
10057        "one\ntwo\nthree\n"
10058    );
10059}
10060
10061#[gpui::test]
10062async fn test_multiple_formatters(cx: &mut TestAppContext) {
10063    init_test(cx, |settings| {
10064        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10065        settings.defaults.formatter =
10066            Some(language_settings::SelectedFormatter::List(FormatterList(
10067                vec![
10068                    Formatter::LanguageServer { name: None },
10069                    Formatter::CodeActions(
10070                        [
10071                            ("code-action-1".into(), true),
10072                            ("code-action-2".into(), true),
10073                        ]
10074                        .into_iter()
10075                        .collect(),
10076                    ),
10077                ]
10078                .into(),
10079            )))
10080    });
10081
10082    let fs = FakeFs::new(cx.executor());
10083    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10084        .await;
10085
10086    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10087    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10088    language_registry.add(rust_lang());
10089
10090    let mut fake_servers = language_registry.register_fake_lsp(
10091        "Rust",
10092        FakeLspAdapter {
10093            capabilities: lsp::ServerCapabilities {
10094                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10095                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10096                    commands: vec!["the-command-for-code-action-1".into()],
10097                    ..Default::default()
10098                }),
10099                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10100                ..Default::default()
10101            },
10102            ..Default::default()
10103        },
10104    );
10105
10106    let buffer = project
10107        .update(cx, |project, cx| {
10108            project.open_local_buffer(path!("/file.rs"), cx)
10109        })
10110        .await
10111        .unwrap();
10112
10113    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10114    let (editor, cx) = cx.add_window_view(|window, cx| {
10115        build_editor_with_project(project.clone(), buffer, window, cx)
10116    });
10117
10118    cx.executor().start_waiting();
10119
10120    let fake_server = fake_servers.next().await.unwrap();
10121    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10122        move |_params, _| async move {
10123            Ok(Some(vec![lsp::TextEdit::new(
10124                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10125                "applied-formatting\n".to_string(),
10126            )]))
10127        },
10128    );
10129    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10130        move |params, _| async move {
10131            assert_eq!(
10132                params.context.only,
10133                Some(vec!["code-action-1".into(), "code-action-2".into()])
10134            );
10135            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10136            Ok(Some(vec![
10137                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10138                    kind: Some("code-action-1".into()),
10139                    edit: Some(lsp::WorkspaceEdit::new(
10140                        [(
10141                            uri.clone(),
10142                            vec![lsp::TextEdit::new(
10143                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10144                                "applied-code-action-1-edit\n".to_string(),
10145                            )],
10146                        )]
10147                        .into_iter()
10148                        .collect(),
10149                    )),
10150                    command: Some(lsp::Command {
10151                        command: "the-command-for-code-action-1".into(),
10152                        ..Default::default()
10153                    }),
10154                    ..Default::default()
10155                }),
10156                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10157                    kind: Some("code-action-2".into()),
10158                    edit: Some(lsp::WorkspaceEdit::new(
10159                        [(
10160                            uri.clone(),
10161                            vec![lsp::TextEdit::new(
10162                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10163                                "applied-code-action-2-edit\n".to_string(),
10164                            )],
10165                        )]
10166                        .into_iter()
10167                        .collect(),
10168                    )),
10169                    ..Default::default()
10170                }),
10171            ]))
10172        },
10173    );
10174
10175    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10176        move |params, _| async move { Ok(params) }
10177    });
10178
10179    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10180    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10181        let fake = fake_server.clone();
10182        let lock = command_lock.clone();
10183        move |params, _| {
10184            assert_eq!(params.command, "the-command-for-code-action-1");
10185            let fake = fake.clone();
10186            let lock = lock.clone();
10187            async move {
10188                lock.lock().await;
10189                fake.server
10190                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10191                        label: None,
10192                        edit: lsp::WorkspaceEdit {
10193                            changes: Some(
10194                                [(
10195                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10196                                    vec![lsp::TextEdit {
10197                                        range: lsp::Range::new(
10198                                            lsp::Position::new(0, 0),
10199                                            lsp::Position::new(0, 0),
10200                                        ),
10201                                        new_text: "applied-code-action-1-command\n".into(),
10202                                    }],
10203                                )]
10204                                .into_iter()
10205                                .collect(),
10206                            ),
10207                            ..Default::default()
10208                        },
10209                    })
10210                    .await
10211                    .into_response()
10212                    .unwrap();
10213                Ok(Some(json!(null)))
10214            }
10215        }
10216    });
10217
10218    cx.executor().start_waiting();
10219    editor
10220        .update_in(cx, |editor, window, cx| {
10221            editor.perform_format(
10222                project.clone(),
10223                FormatTrigger::Manual,
10224                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10225                window,
10226                cx,
10227            )
10228        })
10229        .unwrap()
10230        .await;
10231    editor.update(cx, |editor, cx| {
10232        assert_eq!(
10233            editor.text(cx),
10234            r#"
10235                applied-code-action-2-edit
10236                applied-code-action-1-command
10237                applied-code-action-1-edit
10238                applied-formatting
10239                one
10240                two
10241                three
10242            "#
10243            .unindent()
10244        );
10245    });
10246
10247    editor.update_in(cx, |editor, window, cx| {
10248        editor.undo(&Default::default(), window, cx);
10249        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10250    });
10251
10252    // Perform a manual edit while waiting for an LSP command
10253    // that's being run as part of a formatting code action.
10254    let lock_guard = command_lock.lock().await;
10255    let format = editor
10256        .update_in(cx, |editor, window, cx| {
10257            editor.perform_format(
10258                project.clone(),
10259                FormatTrigger::Manual,
10260                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10261                window,
10262                cx,
10263            )
10264        })
10265        .unwrap();
10266    cx.run_until_parked();
10267    editor.update(cx, |editor, cx| {
10268        assert_eq!(
10269            editor.text(cx),
10270            r#"
10271                applied-code-action-1-edit
10272                applied-formatting
10273                one
10274                two
10275                three
10276            "#
10277            .unindent()
10278        );
10279
10280        editor.buffer.update(cx, |buffer, cx| {
10281            let ix = buffer.len(cx);
10282            buffer.edit([(ix..ix, "edited\n")], None, cx);
10283        });
10284    });
10285
10286    // Allow the LSP command to proceed. Because the buffer was edited,
10287    // the second code action will not be run.
10288    drop(lock_guard);
10289    format.await;
10290    editor.update_in(cx, |editor, window, cx| {
10291        assert_eq!(
10292            editor.text(cx),
10293            r#"
10294                applied-code-action-1-command
10295                applied-code-action-1-edit
10296                applied-formatting
10297                one
10298                two
10299                three
10300                edited
10301            "#
10302            .unindent()
10303        );
10304
10305        // The manual edit is undone first, because it is the last thing the user did
10306        // (even though the command completed afterwards).
10307        editor.undo(&Default::default(), window, cx);
10308        assert_eq!(
10309            editor.text(cx),
10310            r#"
10311                applied-code-action-1-command
10312                applied-code-action-1-edit
10313                applied-formatting
10314                one
10315                two
10316                three
10317            "#
10318            .unindent()
10319        );
10320
10321        // All the formatting (including the command, which completed after the manual edit)
10322        // is undone together.
10323        editor.undo(&Default::default(), window, cx);
10324        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10325    });
10326}
10327
10328#[gpui::test]
10329async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10330    init_test(cx, |settings| {
10331        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10332            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
10333        ))
10334    });
10335
10336    let fs = FakeFs::new(cx.executor());
10337    fs.insert_file(path!("/file.ts"), Default::default()).await;
10338
10339    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10340
10341    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10342    language_registry.add(Arc::new(Language::new(
10343        LanguageConfig {
10344            name: "TypeScript".into(),
10345            matcher: LanguageMatcher {
10346                path_suffixes: vec!["ts".to_string()],
10347                ..Default::default()
10348            },
10349            ..LanguageConfig::default()
10350        },
10351        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10352    )));
10353    update_test_language_settings(cx, |settings| {
10354        settings.defaults.prettier = Some(PrettierSettings {
10355            allowed: true,
10356            ..PrettierSettings::default()
10357        });
10358    });
10359    let mut fake_servers = language_registry.register_fake_lsp(
10360        "TypeScript",
10361        FakeLspAdapter {
10362            capabilities: lsp::ServerCapabilities {
10363                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10364                ..Default::default()
10365            },
10366            ..Default::default()
10367        },
10368    );
10369
10370    let buffer = project
10371        .update(cx, |project, cx| {
10372            project.open_local_buffer(path!("/file.ts"), cx)
10373        })
10374        .await
10375        .unwrap();
10376
10377    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10378    let (editor, cx) = cx.add_window_view(|window, cx| {
10379        build_editor_with_project(project.clone(), buffer, window, cx)
10380    });
10381    editor.update_in(cx, |editor, window, cx| {
10382        editor.set_text(
10383            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10384            window,
10385            cx,
10386        )
10387    });
10388
10389    cx.executor().start_waiting();
10390    let fake_server = fake_servers.next().await.unwrap();
10391
10392    let format = editor
10393        .update_in(cx, |editor, window, cx| {
10394            editor.perform_code_action_kind(
10395                project.clone(),
10396                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10397                window,
10398                cx,
10399            )
10400        })
10401        .unwrap();
10402    fake_server
10403        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10404            assert_eq!(
10405                params.text_document.uri,
10406                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10407            );
10408            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10409                lsp::CodeAction {
10410                    title: "Organize Imports".to_string(),
10411                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10412                    edit: Some(lsp::WorkspaceEdit {
10413                        changes: Some(
10414                            [(
10415                                params.text_document.uri.clone(),
10416                                vec![lsp::TextEdit::new(
10417                                    lsp::Range::new(
10418                                        lsp::Position::new(1, 0),
10419                                        lsp::Position::new(2, 0),
10420                                    ),
10421                                    "".to_string(),
10422                                )],
10423                            )]
10424                            .into_iter()
10425                            .collect(),
10426                        ),
10427                        ..Default::default()
10428                    }),
10429                    ..Default::default()
10430                },
10431            )]))
10432        })
10433        .next()
10434        .await;
10435    cx.executor().start_waiting();
10436    format.await;
10437    assert_eq!(
10438        editor.update(cx, |editor, cx| editor.text(cx)),
10439        "import { a } from 'module';\n\nconst x = a;\n"
10440    );
10441
10442    editor.update_in(cx, |editor, window, cx| {
10443        editor.set_text(
10444            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10445            window,
10446            cx,
10447        )
10448    });
10449    // Ensure we don't lock if code action hangs.
10450    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10451        move |params, _| async move {
10452            assert_eq!(
10453                params.text_document.uri,
10454                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10455            );
10456            futures::future::pending::<()>().await;
10457            unreachable!()
10458        },
10459    );
10460    let format = editor
10461        .update_in(cx, |editor, window, cx| {
10462            editor.perform_code_action_kind(
10463                project,
10464                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10465                window,
10466                cx,
10467            )
10468        })
10469        .unwrap();
10470    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10471    cx.executor().start_waiting();
10472    format.await;
10473    assert_eq!(
10474        editor.update(cx, |editor, cx| editor.text(cx)),
10475        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10476    );
10477}
10478
10479#[gpui::test]
10480async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10481    init_test(cx, |_| {});
10482
10483    let mut cx = EditorLspTestContext::new_rust(
10484        lsp::ServerCapabilities {
10485            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10486            ..Default::default()
10487        },
10488        cx,
10489    )
10490    .await;
10491
10492    cx.set_state(indoc! {"
10493        one.twoˇ
10494    "});
10495
10496    // The format request takes a long time. When it completes, it inserts
10497    // a newline and an indent before the `.`
10498    cx.lsp
10499        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10500            let executor = cx.background_executor().clone();
10501            async move {
10502                executor.timer(Duration::from_millis(100)).await;
10503                Ok(Some(vec![lsp::TextEdit {
10504                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10505                    new_text: "\n    ".into(),
10506                }]))
10507            }
10508        });
10509
10510    // Submit a format request.
10511    let format_1 = cx
10512        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10513        .unwrap();
10514    cx.executor().run_until_parked();
10515
10516    // Submit a second format request.
10517    let format_2 = cx
10518        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10519        .unwrap();
10520    cx.executor().run_until_parked();
10521
10522    // Wait for both format requests to complete
10523    cx.executor().advance_clock(Duration::from_millis(200));
10524    cx.executor().start_waiting();
10525    format_1.await.unwrap();
10526    cx.executor().start_waiting();
10527    format_2.await.unwrap();
10528
10529    // The formatting edits only happens once.
10530    cx.assert_editor_state(indoc! {"
10531        one
10532            .twoˇ
10533    "});
10534}
10535
10536#[gpui::test]
10537async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10538    init_test(cx, |settings| {
10539        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10540    });
10541
10542    let mut cx = EditorLspTestContext::new_rust(
10543        lsp::ServerCapabilities {
10544            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10545            ..Default::default()
10546        },
10547        cx,
10548    )
10549    .await;
10550
10551    // Set up a buffer white some trailing whitespace and no trailing newline.
10552    cx.set_state(
10553        &[
10554            "one ",   //
10555            "twoˇ",   //
10556            "three ", //
10557            "four",   //
10558        ]
10559        .join("\n"),
10560    );
10561
10562    // Submit a format request.
10563    let format = cx
10564        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10565        .unwrap();
10566
10567    // Record which buffer changes have been sent to the language server
10568    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10569    cx.lsp
10570        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10571            let buffer_changes = buffer_changes.clone();
10572            move |params, _| {
10573                buffer_changes.lock().extend(
10574                    params
10575                        .content_changes
10576                        .into_iter()
10577                        .map(|e| (e.range.unwrap(), e.text)),
10578                );
10579            }
10580        });
10581
10582    // Handle formatting requests to the language server.
10583    cx.lsp
10584        .set_request_handler::<lsp::request::Formatting, _, _>({
10585            let buffer_changes = buffer_changes.clone();
10586            move |_, _| {
10587                // When formatting is requested, trailing whitespace has already been stripped,
10588                // and the trailing newline has already been added.
10589                assert_eq!(
10590                    &buffer_changes.lock()[1..],
10591                    &[
10592                        (
10593                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10594                            "".into()
10595                        ),
10596                        (
10597                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10598                            "".into()
10599                        ),
10600                        (
10601                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10602                            "\n".into()
10603                        ),
10604                    ]
10605                );
10606
10607                // Insert blank lines between each line of the buffer.
10608                async move {
10609                    Ok(Some(vec![
10610                        lsp::TextEdit {
10611                            range: lsp::Range::new(
10612                                lsp::Position::new(1, 0),
10613                                lsp::Position::new(1, 0),
10614                            ),
10615                            new_text: "\n".into(),
10616                        },
10617                        lsp::TextEdit {
10618                            range: lsp::Range::new(
10619                                lsp::Position::new(2, 0),
10620                                lsp::Position::new(2, 0),
10621                            ),
10622                            new_text: "\n".into(),
10623                        },
10624                    ]))
10625                }
10626            }
10627        });
10628
10629    // After formatting the buffer, the trailing whitespace is stripped,
10630    // a newline is appended, and the edits provided by the language server
10631    // have been applied.
10632    format.await.unwrap();
10633    cx.assert_editor_state(
10634        &[
10635            "one",   //
10636            "",      //
10637            "twoˇ",  //
10638            "",      //
10639            "three", //
10640            "four",  //
10641            "",      //
10642        ]
10643        .join("\n"),
10644    );
10645
10646    // Undoing the formatting undoes the trailing whitespace removal, the
10647    // trailing newline, and the LSP edits.
10648    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10649    cx.assert_editor_state(
10650        &[
10651            "one ",   //
10652            "twoˇ",   //
10653            "three ", //
10654            "four",   //
10655        ]
10656        .join("\n"),
10657    );
10658}
10659
10660#[gpui::test]
10661async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10662    cx: &mut TestAppContext,
10663) {
10664    init_test(cx, |_| {});
10665
10666    cx.update(|cx| {
10667        cx.update_global::<SettingsStore, _>(|settings, cx| {
10668            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10669                settings.auto_signature_help = Some(true);
10670            });
10671        });
10672    });
10673
10674    let mut cx = EditorLspTestContext::new_rust(
10675        lsp::ServerCapabilities {
10676            signature_help_provider: Some(lsp::SignatureHelpOptions {
10677                ..Default::default()
10678            }),
10679            ..Default::default()
10680        },
10681        cx,
10682    )
10683    .await;
10684
10685    let language = Language::new(
10686        LanguageConfig {
10687            name: "Rust".into(),
10688            brackets: BracketPairConfig {
10689                pairs: vec![
10690                    BracketPair {
10691                        start: "{".to_string(),
10692                        end: "}".to_string(),
10693                        close: true,
10694                        surround: true,
10695                        newline: true,
10696                    },
10697                    BracketPair {
10698                        start: "(".to_string(),
10699                        end: ")".to_string(),
10700                        close: true,
10701                        surround: true,
10702                        newline: true,
10703                    },
10704                    BracketPair {
10705                        start: "/*".to_string(),
10706                        end: " */".to_string(),
10707                        close: true,
10708                        surround: true,
10709                        newline: true,
10710                    },
10711                    BracketPair {
10712                        start: "[".to_string(),
10713                        end: "]".to_string(),
10714                        close: false,
10715                        surround: false,
10716                        newline: true,
10717                    },
10718                    BracketPair {
10719                        start: "\"".to_string(),
10720                        end: "\"".to_string(),
10721                        close: true,
10722                        surround: true,
10723                        newline: false,
10724                    },
10725                    BracketPair {
10726                        start: "<".to_string(),
10727                        end: ">".to_string(),
10728                        close: false,
10729                        surround: true,
10730                        newline: true,
10731                    },
10732                ],
10733                ..Default::default()
10734            },
10735            autoclose_before: "})]".to_string(),
10736            ..Default::default()
10737        },
10738        Some(tree_sitter_rust::LANGUAGE.into()),
10739    );
10740    let language = Arc::new(language);
10741
10742    cx.language_registry().add(language.clone());
10743    cx.update_buffer(|buffer, cx| {
10744        buffer.set_language(Some(language), cx);
10745    });
10746
10747    cx.set_state(
10748        &r#"
10749            fn main() {
10750                sampleˇ
10751            }
10752        "#
10753        .unindent(),
10754    );
10755
10756    cx.update_editor(|editor, window, cx| {
10757        editor.handle_input("(", window, cx);
10758    });
10759    cx.assert_editor_state(
10760        &"
10761            fn main() {
10762                sample(ˇ)
10763            }
10764        "
10765        .unindent(),
10766    );
10767
10768    let mocked_response = lsp::SignatureHelp {
10769        signatures: vec![lsp::SignatureInformation {
10770            label: "fn sample(param1: u8, param2: u8)".to_string(),
10771            documentation: None,
10772            parameters: Some(vec![
10773                lsp::ParameterInformation {
10774                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10775                    documentation: None,
10776                },
10777                lsp::ParameterInformation {
10778                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10779                    documentation: None,
10780                },
10781            ]),
10782            active_parameter: None,
10783        }],
10784        active_signature: Some(0),
10785        active_parameter: Some(0),
10786    };
10787    handle_signature_help_request(&mut cx, mocked_response).await;
10788
10789    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10790        .await;
10791
10792    cx.editor(|editor, _, _| {
10793        let signature_help_state = editor.signature_help_state.popover().cloned();
10794        assert_eq!(
10795            signature_help_state.unwrap().label,
10796            "param1: u8, param2: u8"
10797        );
10798    });
10799}
10800
10801#[gpui::test]
10802async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10803    init_test(cx, |_| {});
10804
10805    cx.update(|cx| {
10806        cx.update_global::<SettingsStore, _>(|settings, cx| {
10807            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10808                settings.auto_signature_help = Some(false);
10809                settings.show_signature_help_after_edits = Some(false);
10810            });
10811        });
10812    });
10813
10814    let mut cx = EditorLspTestContext::new_rust(
10815        lsp::ServerCapabilities {
10816            signature_help_provider: Some(lsp::SignatureHelpOptions {
10817                ..Default::default()
10818            }),
10819            ..Default::default()
10820        },
10821        cx,
10822    )
10823    .await;
10824
10825    let language = Language::new(
10826        LanguageConfig {
10827            name: "Rust".into(),
10828            brackets: BracketPairConfig {
10829                pairs: vec![
10830                    BracketPair {
10831                        start: "{".to_string(),
10832                        end: "}".to_string(),
10833                        close: true,
10834                        surround: true,
10835                        newline: true,
10836                    },
10837                    BracketPair {
10838                        start: "(".to_string(),
10839                        end: ")".to_string(),
10840                        close: true,
10841                        surround: true,
10842                        newline: true,
10843                    },
10844                    BracketPair {
10845                        start: "/*".to_string(),
10846                        end: " */".to_string(),
10847                        close: true,
10848                        surround: true,
10849                        newline: true,
10850                    },
10851                    BracketPair {
10852                        start: "[".to_string(),
10853                        end: "]".to_string(),
10854                        close: false,
10855                        surround: false,
10856                        newline: true,
10857                    },
10858                    BracketPair {
10859                        start: "\"".to_string(),
10860                        end: "\"".to_string(),
10861                        close: true,
10862                        surround: true,
10863                        newline: false,
10864                    },
10865                    BracketPair {
10866                        start: "<".to_string(),
10867                        end: ">".to_string(),
10868                        close: false,
10869                        surround: true,
10870                        newline: true,
10871                    },
10872                ],
10873                ..Default::default()
10874            },
10875            autoclose_before: "})]".to_string(),
10876            ..Default::default()
10877        },
10878        Some(tree_sitter_rust::LANGUAGE.into()),
10879    );
10880    let language = Arc::new(language);
10881
10882    cx.language_registry().add(language.clone());
10883    cx.update_buffer(|buffer, cx| {
10884        buffer.set_language(Some(language), cx);
10885    });
10886
10887    // Ensure that signature_help is not called when no signature help is enabled.
10888    cx.set_state(
10889        &r#"
10890            fn main() {
10891                sampleˇ
10892            }
10893        "#
10894        .unindent(),
10895    );
10896    cx.update_editor(|editor, window, cx| {
10897        editor.handle_input("(", window, cx);
10898    });
10899    cx.assert_editor_state(
10900        &"
10901            fn main() {
10902                sample(ˇ)
10903            }
10904        "
10905        .unindent(),
10906    );
10907    cx.editor(|editor, _, _| {
10908        assert!(editor.signature_help_state.task().is_none());
10909    });
10910
10911    let mocked_response = lsp::SignatureHelp {
10912        signatures: vec![lsp::SignatureInformation {
10913            label: "fn sample(param1: u8, param2: u8)".to_string(),
10914            documentation: None,
10915            parameters: Some(vec![
10916                lsp::ParameterInformation {
10917                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10918                    documentation: None,
10919                },
10920                lsp::ParameterInformation {
10921                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10922                    documentation: None,
10923                },
10924            ]),
10925            active_parameter: None,
10926        }],
10927        active_signature: Some(0),
10928        active_parameter: Some(0),
10929    };
10930
10931    // Ensure that signature_help is called when enabled afte edits
10932    cx.update(|_, cx| {
10933        cx.update_global::<SettingsStore, _>(|settings, cx| {
10934            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10935                settings.auto_signature_help = Some(false);
10936                settings.show_signature_help_after_edits = Some(true);
10937            });
10938        });
10939    });
10940    cx.set_state(
10941        &r#"
10942            fn main() {
10943                sampleˇ
10944            }
10945        "#
10946        .unindent(),
10947    );
10948    cx.update_editor(|editor, window, cx| {
10949        editor.handle_input("(", window, cx);
10950    });
10951    cx.assert_editor_state(
10952        &"
10953            fn main() {
10954                sample(ˇ)
10955            }
10956        "
10957        .unindent(),
10958    );
10959    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10960    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10961        .await;
10962    cx.update_editor(|editor, _, _| {
10963        let signature_help_state = editor.signature_help_state.popover().cloned();
10964        assert!(signature_help_state.is_some());
10965        assert_eq!(
10966            signature_help_state.unwrap().label,
10967            "param1: u8, param2: u8"
10968        );
10969        editor.signature_help_state = SignatureHelpState::default();
10970    });
10971
10972    // Ensure that signature_help is called when auto signature help override is enabled
10973    cx.update(|_, cx| {
10974        cx.update_global::<SettingsStore, _>(|settings, cx| {
10975            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10976                settings.auto_signature_help = Some(true);
10977                settings.show_signature_help_after_edits = Some(false);
10978            });
10979        });
10980    });
10981    cx.set_state(
10982        &r#"
10983            fn main() {
10984                sampleˇ
10985            }
10986        "#
10987        .unindent(),
10988    );
10989    cx.update_editor(|editor, window, cx| {
10990        editor.handle_input("(", window, cx);
10991    });
10992    cx.assert_editor_state(
10993        &"
10994            fn main() {
10995                sample(ˇ)
10996            }
10997        "
10998        .unindent(),
10999    );
11000    handle_signature_help_request(&mut cx, mocked_response).await;
11001    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11002        .await;
11003    cx.editor(|editor, _, _| {
11004        let signature_help_state = editor.signature_help_state.popover().cloned();
11005        assert!(signature_help_state.is_some());
11006        assert_eq!(
11007            signature_help_state.unwrap().label,
11008            "param1: u8, param2: u8"
11009        );
11010    });
11011}
11012
11013#[gpui::test]
11014async fn test_signature_help(cx: &mut TestAppContext) {
11015    init_test(cx, |_| {});
11016    cx.update(|cx| {
11017        cx.update_global::<SettingsStore, _>(|settings, cx| {
11018            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11019                settings.auto_signature_help = Some(true);
11020            });
11021        });
11022    });
11023
11024    let mut cx = EditorLspTestContext::new_rust(
11025        lsp::ServerCapabilities {
11026            signature_help_provider: Some(lsp::SignatureHelpOptions {
11027                ..Default::default()
11028            }),
11029            ..Default::default()
11030        },
11031        cx,
11032    )
11033    .await;
11034
11035    // A test that directly calls `show_signature_help`
11036    cx.update_editor(|editor, window, cx| {
11037        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11038    });
11039
11040    let mocked_response = lsp::SignatureHelp {
11041        signatures: vec![lsp::SignatureInformation {
11042            label: "fn sample(param1: u8, param2: u8)".to_string(),
11043            documentation: None,
11044            parameters: Some(vec![
11045                lsp::ParameterInformation {
11046                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11047                    documentation: None,
11048                },
11049                lsp::ParameterInformation {
11050                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11051                    documentation: None,
11052                },
11053            ]),
11054            active_parameter: None,
11055        }],
11056        active_signature: Some(0),
11057        active_parameter: Some(0),
11058    };
11059    handle_signature_help_request(&mut cx, mocked_response).await;
11060
11061    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11062        .await;
11063
11064    cx.editor(|editor, _, _| {
11065        let signature_help_state = editor.signature_help_state.popover().cloned();
11066        assert!(signature_help_state.is_some());
11067        assert_eq!(
11068            signature_help_state.unwrap().label,
11069            "param1: u8, param2: u8"
11070        );
11071    });
11072
11073    // When exiting outside from inside the brackets, `signature_help` is closed.
11074    cx.set_state(indoc! {"
11075        fn main() {
11076            sample(ˇ);
11077        }
11078
11079        fn sample(param1: u8, param2: u8) {}
11080    "});
11081
11082    cx.update_editor(|editor, window, cx| {
11083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11084            s.select_ranges([0..0])
11085        });
11086    });
11087
11088    let mocked_response = lsp::SignatureHelp {
11089        signatures: Vec::new(),
11090        active_signature: None,
11091        active_parameter: None,
11092    };
11093    handle_signature_help_request(&mut cx, mocked_response).await;
11094
11095    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11096        .await;
11097
11098    cx.editor(|editor, _, _| {
11099        assert!(!editor.signature_help_state.is_shown());
11100    });
11101
11102    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11103    cx.set_state(indoc! {"
11104        fn main() {
11105            sample(ˇ);
11106        }
11107
11108        fn sample(param1: u8, param2: u8) {}
11109    "});
11110
11111    let mocked_response = lsp::SignatureHelp {
11112        signatures: vec![lsp::SignatureInformation {
11113            label: "fn sample(param1: u8, param2: u8)".to_string(),
11114            documentation: None,
11115            parameters: Some(vec![
11116                lsp::ParameterInformation {
11117                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11118                    documentation: None,
11119                },
11120                lsp::ParameterInformation {
11121                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11122                    documentation: None,
11123                },
11124            ]),
11125            active_parameter: None,
11126        }],
11127        active_signature: Some(0),
11128        active_parameter: Some(0),
11129    };
11130    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11131    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11132        .await;
11133    cx.editor(|editor, _, _| {
11134        assert!(editor.signature_help_state.is_shown());
11135    });
11136
11137    // Restore the popover with more parameter input
11138    cx.set_state(indoc! {"
11139        fn main() {
11140            sample(param1, param2ˇ);
11141        }
11142
11143        fn sample(param1: u8, param2: u8) {}
11144    "});
11145
11146    let mocked_response = lsp::SignatureHelp {
11147        signatures: vec![lsp::SignatureInformation {
11148            label: "fn sample(param1: u8, param2: u8)".to_string(),
11149            documentation: None,
11150            parameters: Some(vec![
11151                lsp::ParameterInformation {
11152                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11153                    documentation: None,
11154                },
11155                lsp::ParameterInformation {
11156                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11157                    documentation: None,
11158                },
11159            ]),
11160            active_parameter: None,
11161        }],
11162        active_signature: Some(0),
11163        active_parameter: Some(1),
11164    };
11165    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11166    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11167        .await;
11168
11169    // When selecting a range, the popover is gone.
11170    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11171    cx.update_editor(|editor, window, cx| {
11172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11173            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11174        })
11175    });
11176    cx.assert_editor_state(indoc! {"
11177        fn main() {
11178            sample(param1, «ˇparam2»);
11179        }
11180
11181        fn sample(param1: u8, param2: u8) {}
11182    "});
11183    cx.editor(|editor, _, _| {
11184        assert!(!editor.signature_help_state.is_shown());
11185    });
11186
11187    // When unselecting again, the popover is back if within the brackets.
11188    cx.update_editor(|editor, window, cx| {
11189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11190            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11191        })
11192    });
11193    cx.assert_editor_state(indoc! {"
11194        fn main() {
11195            sample(param1, ˇparam2);
11196        }
11197
11198        fn sample(param1: u8, param2: u8) {}
11199    "});
11200    handle_signature_help_request(&mut cx, mocked_response).await;
11201    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11202        .await;
11203    cx.editor(|editor, _, _| {
11204        assert!(editor.signature_help_state.is_shown());
11205    });
11206
11207    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11208    cx.update_editor(|editor, window, cx| {
11209        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11210            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11211            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11212        })
11213    });
11214    cx.assert_editor_state(indoc! {"
11215        fn main() {
11216            sample(param1, ˇparam2);
11217        }
11218
11219        fn sample(param1: u8, param2: u8) {}
11220    "});
11221
11222    let mocked_response = lsp::SignatureHelp {
11223        signatures: vec![lsp::SignatureInformation {
11224            label: "fn sample(param1: u8, param2: u8)".to_string(),
11225            documentation: None,
11226            parameters: Some(vec![
11227                lsp::ParameterInformation {
11228                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11229                    documentation: None,
11230                },
11231                lsp::ParameterInformation {
11232                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11233                    documentation: None,
11234                },
11235            ]),
11236            active_parameter: None,
11237        }],
11238        active_signature: Some(0),
11239        active_parameter: Some(1),
11240    };
11241    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11242    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11243        .await;
11244    cx.update_editor(|editor, _, cx| {
11245        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11246    });
11247    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11248        .await;
11249    cx.update_editor(|editor, window, cx| {
11250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11251            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11252        })
11253    });
11254    cx.assert_editor_state(indoc! {"
11255        fn main() {
11256            sample(param1, «ˇparam2»);
11257        }
11258
11259        fn sample(param1: u8, param2: u8) {}
11260    "});
11261    cx.update_editor(|editor, window, cx| {
11262        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11263            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11264        })
11265    });
11266    cx.assert_editor_state(indoc! {"
11267        fn main() {
11268            sample(param1, ˇparam2);
11269        }
11270
11271        fn sample(param1: u8, param2: u8) {}
11272    "});
11273    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11274        .await;
11275}
11276
11277#[gpui::test]
11278async fn test_completion_mode(cx: &mut TestAppContext) {
11279    init_test(cx, |_| {});
11280    let mut cx = EditorLspTestContext::new_rust(
11281        lsp::ServerCapabilities {
11282            completion_provider: Some(lsp::CompletionOptions {
11283                resolve_provider: Some(true),
11284                ..Default::default()
11285            }),
11286            ..Default::default()
11287        },
11288        cx,
11289    )
11290    .await;
11291
11292    struct Run {
11293        run_description: &'static str,
11294        initial_state: String,
11295        buffer_marked_text: String,
11296        completion_label: &'static str,
11297        completion_text: &'static str,
11298        expected_with_insert_mode: String,
11299        expected_with_replace_mode: String,
11300        expected_with_replace_subsequence_mode: String,
11301        expected_with_replace_suffix_mode: String,
11302    }
11303
11304    let runs = [
11305        Run {
11306            run_description: "Start of word matches completion text",
11307            initial_state: "before ediˇ after".into(),
11308            buffer_marked_text: "before <edi|> after".into(),
11309            completion_label: "editor",
11310            completion_text: "editor",
11311            expected_with_insert_mode: "before editorˇ after".into(),
11312            expected_with_replace_mode: "before editorˇ after".into(),
11313            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11314            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11315        },
11316        Run {
11317            run_description: "Accept same text at the middle of the word",
11318            initial_state: "before ediˇtor after".into(),
11319            buffer_marked_text: "before <edi|tor> after".into(),
11320            completion_label: "editor",
11321            completion_text: "editor",
11322            expected_with_insert_mode: "before editorˇtor after".into(),
11323            expected_with_replace_mode: "before editorˇ after".into(),
11324            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11325            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11326        },
11327        Run {
11328            run_description: "End of word matches completion text -- cursor at end",
11329            initial_state: "before torˇ after".into(),
11330            buffer_marked_text: "before <tor|> after".into(),
11331            completion_label: "editor",
11332            completion_text: "editor",
11333            expected_with_insert_mode: "before editorˇ after".into(),
11334            expected_with_replace_mode: "before editorˇ after".into(),
11335            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11336            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11337        },
11338        Run {
11339            run_description: "End of word matches completion text -- cursor at start",
11340            initial_state: "before ˇtor after".into(),
11341            buffer_marked_text: "before <|tor> after".into(),
11342            completion_label: "editor",
11343            completion_text: "editor",
11344            expected_with_insert_mode: "before editorˇtor after".into(),
11345            expected_with_replace_mode: "before editorˇ after".into(),
11346            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11347            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11348        },
11349        Run {
11350            run_description: "Prepend text containing whitespace",
11351            initial_state: "pˇfield: bool".into(),
11352            buffer_marked_text: "<p|field>: bool".into(),
11353            completion_label: "pub ",
11354            completion_text: "pub ",
11355            expected_with_insert_mode: "pub ˇfield: bool".into(),
11356            expected_with_replace_mode: "pub ˇ: bool".into(),
11357            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11358            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11359        },
11360        Run {
11361            run_description: "Add element to start of list",
11362            initial_state: "[element_ˇelement_2]".into(),
11363            buffer_marked_text: "[<element_|element_2>]".into(),
11364            completion_label: "element_1",
11365            completion_text: "element_1",
11366            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11367            expected_with_replace_mode: "[element_1ˇ]".into(),
11368            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11369            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11370        },
11371        Run {
11372            run_description: "Add element to start of list -- first and second elements are equal",
11373            initial_state: "[elˇelement]".into(),
11374            buffer_marked_text: "[<el|element>]".into(),
11375            completion_label: "element",
11376            completion_text: "element",
11377            expected_with_insert_mode: "[elementˇelement]".into(),
11378            expected_with_replace_mode: "[elementˇ]".into(),
11379            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11380            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11381        },
11382        Run {
11383            run_description: "Ends with matching suffix",
11384            initial_state: "SubˇError".into(),
11385            buffer_marked_text: "<Sub|Error>".into(),
11386            completion_label: "SubscriptionError",
11387            completion_text: "SubscriptionError",
11388            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11389            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11390            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11391            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11392        },
11393        Run {
11394            run_description: "Suffix is a subsequence -- contiguous",
11395            initial_state: "SubˇErr".into(),
11396            buffer_marked_text: "<Sub|Err>".into(),
11397            completion_label: "SubscriptionError",
11398            completion_text: "SubscriptionError",
11399            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11400            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11401            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11402            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11403        },
11404        Run {
11405            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11406            initial_state: "Suˇscrirr".into(),
11407            buffer_marked_text: "<Su|scrirr>".into(),
11408            completion_label: "SubscriptionError",
11409            completion_text: "SubscriptionError",
11410            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11411            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11412            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11413            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11414        },
11415        Run {
11416            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11417            initial_state: "foo(indˇix)".into(),
11418            buffer_marked_text: "foo(<ind|ix>)".into(),
11419            completion_label: "node_index",
11420            completion_text: "node_index",
11421            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11422            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11423            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11424            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11425        },
11426        Run {
11427            run_description: "Replace range ends before cursor - should extend to cursor",
11428            initial_state: "before editˇo after".into(),
11429            buffer_marked_text: "before <{ed}>it|o after".into(),
11430            completion_label: "editor",
11431            completion_text: "editor",
11432            expected_with_insert_mode: "before editorˇo after".into(),
11433            expected_with_replace_mode: "before editorˇo after".into(),
11434            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11435            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11436        },
11437        Run {
11438            run_description: "Uses label for suffix matching",
11439            initial_state: "before ediˇtor after".into(),
11440            buffer_marked_text: "before <edi|tor> after".into(),
11441            completion_label: "editor",
11442            completion_text: "editor()",
11443            expected_with_insert_mode: "before editor()ˇtor after".into(),
11444            expected_with_replace_mode: "before editor()ˇ after".into(),
11445            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11446            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11447        },
11448        Run {
11449            run_description: "Case insensitive subsequence and suffix matching",
11450            initial_state: "before EDiˇtoR after".into(),
11451            buffer_marked_text: "before <EDi|toR> after".into(),
11452            completion_label: "editor",
11453            completion_text: "editor",
11454            expected_with_insert_mode: "before editorˇtoR after".into(),
11455            expected_with_replace_mode: "before editorˇ after".into(),
11456            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11457            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11458        },
11459    ];
11460
11461    for run in runs {
11462        let run_variations = [
11463            (LspInsertMode::Insert, run.expected_with_insert_mode),
11464            (LspInsertMode::Replace, run.expected_with_replace_mode),
11465            (
11466                LspInsertMode::ReplaceSubsequence,
11467                run.expected_with_replace_subsequence_mode,
11468            ),
11469            (
11470                LspInsertMode::ReplaceSuffix,
11471                run.expected_with_replace_suffix_mode,
11472            ),
11473        ];
11474
11475        for (lsp_insert_mode, expected_text) in run_variations {
11476            eprintln!(
11477                "run = {:?}, mode = {lsp_insert_mode:.?}",
11478                run.run_description,
11479            );
11480
11481            update_test_language_settings(&mut cx, |settings| {
11482                settings.defaults.completions = Some(CompletionSettings {
11483                    lsp_insert_mode,
11484                    words: WordsCompletionMode::Disabled,
11485                    lsp: true,
11486                    lsp_fetch_timeout_ms: 0,
11487                });
11488            });
11489
11490            cx.set_state(&run.initial_state);
11491            cx.update_editor(|editor, window, cx| {
11492                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11493            });
11494
11495            let counter = Arc::new(AtomicUsize::new(0));
11496            handle_completion_request_with_insert_and_replace(
11497                &mut cx,
11498                &run.buffer_marked_text,
11499                vec![(run.completion_label, run.completion_text)],
11500                counter.clone(),
11501            )
11502            .await;
11503            cx.condition(|editor, _| editor.context_menu_visible())
11504                .await;
11505            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11506
11507            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11508                editor
11509                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11510                    .unwrap()
11511            });
11512            cx.assert_editor_state(&expected_text);
11513            handle_resolve_completion_request(&mut cx, None).await;
11514            apply_additional_edits.await.unwrap();
11515        }
11516    }
11517}
11518
11519#[gpui::test]
11520async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11521    init_test(cx, |_| {});
11522    let mut cx = EditorLspTestContext::new_rust(
11523        lsp::ServerCapabilities {
11524            completion_provider: Some(lsp::CompletionOptions {
11525                resolve_provider: Some(true),
11526                ..Default::default()
11527            }),
11528            ..Default::default()
11529        },
11530        cx,
11531    )
11532    .await;
11533
11534    let initial_state = "SubˇError";
11535    let buffer_marked_text = "<Sub|Error>";
11536    let completion_text = "SubscriptionError";
11537    let expected_with_insert_mode = "SubscriptionErrorˇError";
11538    let expected_with_replace_mode = "SubscriptionErrorˇ";
11539
11540    update_test_language_settings(&mut cx, |settings| {
11541        settings.defaults.completions = Some(CompletionSettings {
11542            words: WordsCompletionMode::Disabled,
11543            // set the opposite here to ensure that the action is overriding the default behavior
11544            lsp_insert_mode: LspInsertMode::Insert,
11545            lsp: true,
11546            lsp_fetch_timeout_ms: 0,
11547        });
11548    });
11549
11550    cx.set_state(initial_state);
11551    cx.update_editor(|editor, window, cx| {
11552        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11553    });
11554
11555    let counter = Arc::new(AtomicUsize::new(0));
11556    handle_completion_request_with_insert_and_replace(
11557        &mut cx,
11558        &buffer_marked_text,
11559        vec![(completion_text, completion_text)],
11560        counter.clone(),
11561    )
11562    .await;
11563    cx.condition(|editor, _| editor.context_menu_visible())
11564        .await;
11565    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11566
11567    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11568        editor
11569            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11570            .unwrap()
11571    });
11572    cx.assert_editor_state(&expected_with_replace_mode);
11573    handle_resolve_completion_request(&mut cx, None).await;
11574    apply_additional_edits.await.unwrap();
11575
11576    update_test_language_settings(&mut cx, |settings| {
11577        settings.defaults.completions = Some(CompletionSettings {
11578            words: WordsCompletionMode::Disabled,
11579            // set the opposite here to ensure that the action is overriding the default behavior
11580            lsp_insert_mode: LspInsertMode::Replace,
11581            lsp: true,
11582            lsp_fetch_timeout_ms: 0,
11583        });
11584    });
11585
11586    cx.set_state(initial_state);
11587    cx.update_editor(|editor, window, cx| {
11588        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11589    });
11590    handle_completion_request_with_insert_and_replace(
11591        &mut cx,
11592        &buffer_marked_text,
11593        vec![(completion_text, completion_text)],
11594        counter.clone(),
11595    )
11596    .await;
11597    cx.condition(|editor, _| editor.context_menu_visible())
11598        .await;
11599    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11600
11601    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11602        editor
11603            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11604            .unwrap()
11605    });
11606    cx.assert_editor_state(&expected_with_insert_mode);
11607    handle_resolve_completion_request(&mut cx, None).await;
11608    apply_additional_edits.await.unwrap();
11609}
11610
11611#[gpui::test]
11612async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11613    init_test(cx, |_| {});
11614    let mut cx = EditorLspTestContext::new_rust(
11615        lsp::ServerCapabilities {
11616            completion_provider: Some(lsp::CompletionOptions {
11617                resolve_provider: Some(true),
11618                ..Default::default()
11619            }),
11620            ..Default::default()
11621        },
11622        cx,
11623    )
11624    .await;
11625
11626    // scenario: surrounding text matches completion text
11627    let completion_text = "to_offset";
11628    let initial_state = indoc! {"
11629        1. buf.to_offˇsuffix
11630        2. buf.to_offˇsuf
11631        3. buf.to_offˇfix
11632        4. buf.to_offˇ
11633        5. into_offˇensive
11634        6. ˇsuffix
11635        7. let ˇ //
11636        8. aaˇzz
11637        9. buf.to_off«zzzzzˇ»suffix
11638        10. buf.«ˇzzzzz»suffix
11639        11. to_off«ˇzzzzz»
11640
11641        buf.to_offˇsuffix  // newest cursor
11642    "};
11643    let completion_marked_buffer = indoc! {"
11644        1. buf.to_offsuffix
11645        2. buf.to_offsuf
11646        3. buf.to_offfix
11647        4. buf.to_off
11648        5. into_offensive
11649        6. suffix
11650        7. let  //
11651        8. aazz
11652        9. buf.to_offzzzzzsuffix
11653        10. buf.zzzzzsuffix
11654        11. to_offzzzzz
11655
11656        buf.<to_off|suffix>  // newest cursor
11657    "};
11658    let expected = indoc! {"
11659        1. buf.to_offsetˇ
11660        2. buf.to_offsetˇsuf
11661        3. buf.to_offsetˇfix
11662        4. buf.to_offsetˇ
11663        5. into_offsetˇensive
11664        6. to_offsetˇsuffix
11665        7. let to_offsetˇ //
11666        8. aato_offsetˇzz
11667        9. buf.to_offsetˇ
11668        10. buf.to_offsetˇsuffix
11669        11. to_offsetˇ
11670
11671        buf.to_offsetˇ  // newest cursor
11672    "};
11673    cx.set_state(initial_state);
11674    cx.update_editor(|editor, window, cx| {
11675        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11676    });
11677    handle_completion_request_with_insert_and_replace(
11678        &mut cx,
11679        completion_marked_buffer,
11680        vec![(completion_text, completion_text)],
11681        Arc::new(AtomicUsize::new(0)),
11682    )
11683    .await;
11684    cx.condition(|editor, _| editor.context_menu_visible())
11685        .await;
11686    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11687        editor
11688            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11689            .unwrap()
11690    });
11691    cx.assert_editor_state(expected);
11692    handle_resolve_completion_request(&mut cx, None).await;
11693    apply_additional_edits.await.unwrap();
11694
11695    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11696    let completion_text = "foo_and_bar";
11697    let initial_state = indoc! {"
11698        1. ooanbˇ
11699        2. zooanbˇ
11700        3. ooanbˇz
11701        4. zooanbˇz
11702        5. ooanˇ
11703        6. oanbˇ
11704
11705        ooanbˇ
11706    "};
11707    let completion_marked_buffer = indoc! {"
11708        1. ooanb
11709        2. zooanb
11710        3. ooanbz
11711        4. zooanbz
11712        5. ooan
11713        6. oanb
11714
11715        <ooanb|>
11716    "};
11717    let expected = indoc! {"
11718        1. foo_and_barˇ
11719        2. zfoo_and_barˇ
11720        3. foo_and_barˇz
11721        4. zfoo_and_barˇz
11722        5. ooanfoo_and_barˇ
11723        6. oanbfoo_and_barˇ
11724
11725        foo_and_barˇ
11726    "};
11727    cx.set_state(initial_state);
11728    cx.update_editor(|editor, window, cx| {
11729        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11730    });
11731    handle_completion_request_with_insert_and_replace(
11732        &mut cx,
11733        completion_marked_buffer,
11734        vec![(completion_text, completion_text)],
11735        Arc::new(AtomicUsize::new(0)),
11736    )
11737    .await;
11738    cx.condition(|editor, _| editor.context_menu_visible())
11739        .await;
11740    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11741        editor
11742            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11743            .unwrap()
11744    });
11745    cx.assert_editor_state(expected);
11746    handle_resolve_completion_request(&mut cx, None).await;
11747    apply_additional_edits.await.unwrap();
11748
11749    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11750    // (expects the same as if it was inserted at the end)
11751    let completion_text = "foo_and_bar";
11752    let initial_state = indoc! {"
11753        1. ooˇanb
11754        2. zooˇanb
11755        3. ooˇanbz
11756        4. zooˇanbz
11757
11758        ooˇanb
11759    "};
11760    let completion_marked_buffer = indoc! {"
11761        1. ooanb
11762        2. zooanb
11763        3. ooanbz
11764        4. zooanbz
11765
11766        <oo|anb>
11767    "};
11768    let expected = indoc! {"
11769        1. foo_and_barˇ
11770        2. zfoo_and_barˇ
11771        3. foo_and_barˇz
11772        4. zfoo_and_barˇz
11773
11774        foo_and_barˇ
11775    "};
11776    cx.set_state(initial_state);
11777    cx.update_editor(|editor, window, cx| {
11778        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11779    });
11780    handle_completion_request_with_insert_and_replace(
11781        &mut cx,
11782        completion_marked_buffer,
11783        vec![(completion_text, completion_text)],
11784        Arc::new(AtomicUsize::new(0)),
11785    )
11786    .await;
11787    cx.condition(|editor, _| editor.context_menu_visible())
11788        .await;
11789    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11790        editor
11791            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11792            .unwrap()
11793    });
11794    cx.assert_editor_state(expected);
11795    handle_resolve_completion_request(&mut cx, None).await;
11796    apply_additional_edits.await.unwrap();
11797}
11798
11799// This used to crash
11800#[gpui::test]
11801async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11802    init_test(cx, |_| {});
11803
11804    let buffer_text = indoc! {"
11805        fn main() {
11806            10.satu;
11807
11808            //
11809            // separate cursors so they open in different excerpts (manually reproducible)
11810            //
11811
11812            10.satu20;
11813        }
11814    "};
11815    let multibuffer_text_with_selections = indoc! {"
11816        fn main() {
11817            10.satuˇ;
11818
11819            //
11820
11821            //
11822
11823            10.satuˇ20;
11824        }
11825    "};
11826    let expected_multibuffer = indoc! {"
11827        fn main() {
11828            10.saturating_sub()ˇ;
11829
11830            //
11831
11832            //
11833
11834            10.saturating_sub()ˇ;
11835        }
11836    "};
11837
11838    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11839    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11840
11841    let fs = FakeFs::new(cx.executor());
11842    fs.insert_tree(
11843        path!("/a"),
11844        json!({
11845            "main.rs": buffer_text,
11846        }),
11847    )
11848    .await;
11849
11850    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11851    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11852    language_registry.add(rust_lang());
11853    let mut fake_servers = language_registry.register_fake_lsp(
11854        "Rust",
11855        FakeLspAdapter {
11856            capabilities: lsp::ServerCapabilities {
11857                completion_provider: Some(lsp::CompletionOptions {
11858                    resolve_provider: None,
11859                    ..lsp::CompletionOptions::default()
11860                }),
11861                ..lsp::ServerCapabilities::default()
11862            },
11863            ..FakeLspAdapter::default()
11864        },
11865    );
11866    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11867    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11868    let buffer = project
11869        .update(cx, |project, cx| {
11870            project.open_local_buffer(path!("/a/main.rs"), cx)
11871        })
11872        .await
11873        .unwrap();
11874
11875    let multi_buffer = cx.new(|cx| {
11876        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11877        multi_buffer.push_excerpts(
11878            buffer.clone(),
11879            [ExcerptRange::new(0..first_excerpt_end)],
11880            cx,
11881        );
11882        multi_buffer.push_excerpts(
11883            buffer.clone(),
11884            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11885            cx,
11886        );
11887        multi_buffer
11888    });
11889
11890    let editor = workspace
11891        .update(cx, |_, window, cx| {
11892            cx.new(|cx| {
11893                Editor::new(
11894                    EditorMode::Full {
11895                        scale_ui_elements_with_buffer_font_size: false,
11896                        show_active_line_background: false,
11897                        sized_by_content: false,
11898                    },
11899                    multi_buffer.clone(),
11900                    Some(project.clone()),
11901                    window,
11902                    cx,
11903                )
11904            })
11905        })
11906        .unwrap();
11907
11908    let pane = workspace
11909        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11910        .unwrap();
11911    pane.update_in(cx, |pane, window, cx| {
11912        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11913    });
11914
11915    let fake_server = fake_servers.next().await.unwrap();
11916
11917    editor.update_in(cx, |editor, window, cx| {
11918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11919            s.select_ranges([
11920                Point::new(1, 11)..Point::new(1, 11),
11921                Point::new(7, 11)..Point::new(7, 11),
11922            ])
11923        });
11924
11925        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11926    });
11927
11928    editor.update_in(cx, |editor, window, cx| {
11929        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11930    });
11931
11932    fake_server
11933        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11934            let completion_item = lsp::CompletionItem {
11935                label: "saturating_sub()".into(),
11936                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11937                    lsp::InsertReplaceEdit {
11938                        new_text: "saturating_sub()".to_owned(),
11939                        insert: lsp::Range::new(
11940                            lsp::Position::new(7, 7),
11941                            lsp::Position::new(7, 11),
11942                        ),
11943                        replace: lsp::Range::new(
11944                            lsp::Position::new(7, 7),
11945                            lsp::Position::new(7, 13),
11946                        ),
11947                    },
11948                )),
11949                ..lsp::CompletionItem::default()
11950            };
11951
11952            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11953        })
11954        .next()
11955        .await
11956        .unwrap();
11957
11958    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11959        .await;
11960
11961    editor
11962        .update_in(cx, |editor, window, cx| {
11963            editor
11964                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11965                .unwrap()
11966        })
11967        .await
11968        .unwrap();
11969
11970    editor.update(cx, |editor, cx| {
11971        assert_text_with_selections(editor, expected_multibuffer, cx);
11972    })
11973}
11974
11975#[gpui::test]
11976async fn test_completion(cx: &mut TestAppContext) {
11977    init_test(cx, |_| {});
11978
11979    let mut cx = EditorLspTestContext::new_rust(
11980        lsp::ServerCapabilities {
11981            completion_provider: Some(lsp::CompletionOptions {
11982                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11983                resolve_provider: Some(true),
11984                ..Default::default()
11985            }),
11986            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11987            ..Default::default()
11988        },
11989        cx,
11990    )
11991    .await;
11992    let counter = Arc::new(AtomicUsize::new(0));
11993
11994    cx.set_state(indoc! {"
11995        oneˇ
11996        two
11997        three
11998    "});
11999    cx.simulate_keystroke(".");
12000    handle_completion_request(
12001        indoc! {"
12002            one.|<>
12003            two
12004            three
12005        "},
12006        vec!["first_completion", "second_completion"],
12007        true,
12008        counter.clone(),
12009        &mut cx,
12010    )
12011    .await;
12012    cx.condition(|editor, _| editor.context_menu_visible())
12013        .await;
12014    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12015
12016    let _handler = handle_signature_help_request(
12017        &mut cx,
12018        lsp::SignatureHelp {
12019            signatures: vec![lsp::SignatureInformation {
12020                label: "test signature".to_string(),
12021                documentation: None,
12022                parameters: Some(vec![lsp::ParameterInformation {
12023                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12024                    documentation: None,
12025                }]),
12026                active_parameter: None,
12027            }],
12028            active_signature: None,
12029            active_parameter: None,
12030        },
12031    );
12032    cx.update_editor(|editor, window, cx| {
12033        assert!(
12034            !editor.signature_help_state.is_shown(),
12035            "No signature help was called for"
12036        );
12037        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12038    });
12039    cx.run_until_parked();
12040    cx.update_editor(|editor, _, _| {
12041        assert!(
12042            !editor.signature_help_state.is_shown(),
12043            "No signature help should be shown when completions menu is open"
12044        );
12045    });
12046
12047    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12048        editor.context_menu_next(&Default::default(), window, cx);
12049        editor
12050            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12051            .unwrap()
12052    });
12053    cx.assert_editor_state(indoc! {"
12054        one.second_completionˇ
12055        two
12056        three
12057    "});
12058
12059    handle_resolve_completion_request(
12060        &mut cx,
12061        Some(vec![
12062            (
12063                //This overlaps with the primary completion edit which is
12064                //misbehavior from the LSP spec, test that we filter it out
12065                indoc! {"
12066                    one.second_ˇcompletion
12067                    two
12068                    threeˇ
12069                "},
12070                "overlapping additional edit",
12071            ),
12072            (
12073                indoc! {"
12074                    one.second_completion
12075                    two
12076                    threeˇ
12077                "},
12078                "\nadditional edit",
12079            ),
12080        ]),
12081    )
12082    .await;
12083    apply_additional_edits.await.unwrap();
12084    cx.assert_editor_state(indoc! {"
12085        one.second_completionˇ
12086        two
12087        three
12088        additional edit
12089    "});
12090
12091    cx.set_state(indoc! {"
12092        one.second_completion
12093        twoˇ
12094        threeˇ
12095        additional edit
12096    "});
12097    cx.simulate_keystroke(" ");
12098    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12099    cx.simulate_keystroke("s");
12100    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12101
12102    cx.assert_editor_state(indoc! {"
12103        one.second_completion
12104        two sˇ
12105        three sˇ
12106        additional edit
12107    "});
12108    handle_completion_request(
12109        indoc! {"
12110            one.second_completion
12111            two s
12112            three <s|>
12113            additional edit
12114        "},
12115        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12116        true,
12117        counter.clone(),
12118        &mut cx,
12119    )
12120    .await;
12121    cx.condition(|editor, _| editor.context_menu_visible())
12122        .await;
12123    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12124
12125    cx.simulate_keystroke("i");
12126
12127    handle_completion_request(
12128        indoc! {"
12129            one.second_completion
12130            two si
12131            three <si|>
12132            additional edit
12133        "},
12134        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12135        true,
12136        counter.clone(),
12137        &mut cx,
12138    )
12139    .await;
12140    cx.condition(|editor, _| editor.context_menu_visible())
12141        .await;
12142    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12143
12144    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12145        editor
12146            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12147            .unwrap()
12148    });
12149    cx.assert_editor_state(indoc! {"
12150        one.second_completion
12151        two sixth_completionˇ
12152        three sixth_completionˇ
12153        additional edit
12154    "});
12155
12156    apply_additional_edits.await.unwrap();
12157
12158    update_test_language_settings(&mut cx, |settings| {
12159        settings.defaults.show_completions_on_input = Some(false);
12160    });
12161    cx.set_state("editorˇ");
12162    cx.simulate_keystroke(".");
12163    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12164    cx.simulate_keystrokes("c l o");
12165    cx.assert_editor_state("editor.cloˇ");
12166    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12167    cx.update_editor(|editor, window, cx| {
12168        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12169    });
12170    handle_completion_request(
12171        "editor.<clo|>",
12172        vec!["close", "clobber"],
12173        true,
12174        counter.clone(),
12175        &mut cx,
12176    )
12177    .await;
12178    cx.condition(|editor, _| editor.context_menu_visible())
12179        .await;
12180    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12181
12182    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12183        editor
12184            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12185            .unwrap()
12186    });
12187    cx.assert_editor_state("editor.clobberˇ");
12188    handle_resolve_completion_request(&mut cx, None).await;
12189    apply_additional_edits.await.unwrap();
12190}
12191
12192#[gpui::test]
12193async fn test_completion_reuse(cx: &mut TestAppContext) {
12194    init_test(cx, |_| {});
12195
12196    let mut cx = EditorLspTestContext::new_rust(
12197        lsp::ServerCapabilities {
12198            completion_provider: Some(lsp::CompletionOptions {
12199                trigger_characters: Some(vec![".".to_string()]),
12200                ..Default::default()
12201            }),
12202            ..Default::default()
12203        },
12204        cx,
12205    )
12206    .await;
12207
12208    let counter = Arc::new(AtomicUsize::new(0));
12209    cx.set_state("objˇ");
12210    cx.simulate_keystroke(".");
12211
12212    // Initial completion request returns complete results
12213    let is_incomplete = false;
12214    handle_completion_request(
12215        "obj.|<>",
12216        vec!["a", "ab", "abc"],
12217        is_incomplete,
12218        counter.clone(),
12219        &mut cx,
12220    )
12221    .await;
12222    cx.run_until_parked();
12223    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12224    cx.assert_editor_state("obj.ˇ");
12225    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12226
12227    // Type "a" - filters existing completions
12228    cx.simulate_keystroke("a");
12229    cx.run_until_parked();
12230    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12231    cx.assert_editor_state("obj.aˇ");
12232    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12233
12234    // Type "b" - filters existing completions
12235    cx.simulate_keystroke("b");
12236    cx.run_until_parked();
12237    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12238    cx.assert_editor_state("obj.abˇ");
12239    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12240
12241    // Type "c" - filters existing completions
12242    cx.simulate_keystroke("c");
12243    cx.run_until_parked();
12244    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12245    cx.assert_editor_state("obj.abcˇ");
12246    check_displayed_completions(vec!["abc"], &mut cx);
12247
12248    // Backspace to delete "c" - filters existing completions
12249    cx.update_editor(|editor, window, cx| {
12250        editor.backspace(&Backspace, window, cx);
12251    });
12252    cx.run_until_parked();
12253    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12254    cx.assert_editor_state("obj.abˇ");
12255    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12256
12257    // Moving cursor to the left dismisses menu.
12258    cx.update_editor(|editor, window, cx| {
12259        editor.move_left(&MoveLeft, window, cx);
12260    });
12261    cx.run_until_parked();
12262    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12263    cx.assert_editor_state("obj.aˇb");
12264    cx.update_editor(|editor, _, _| {
12265        assert_eq!(editor.context_menu_visible(), false);
12266    });
12267
12268    // Type "b" - new request
12269    cx.simulate_keystroke("b");
12270    let is_incomplete = false;
12271    handle_completion_request(
12272        "obj.<ab|>a",
12273        vec!["ab", "abc"],
12274        is_incomplete,
12275        counter.clone(),
12276        &mut cx,
12277    )
12278    .await;
12279    cx.run_until_parked();
12280    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12281    cx.assert_editor_state("obj.abˇb");
12282    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12283
12284    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12285    cx.update_editor(|editor, window, cx| {
12286        editor.backspace(&Backspace, window, cx);
12287    });
12288    let is_incomplete = false;
12289    handle_completion_request(
12290        "obj.<a|>b",
12291        vec!["a", "ab", "abc"],
12292        is_incomplete,
12293        counter.clone(),
12294        &mut cx,
12295    )
12296    .await;
12297    cx.run_until_parked();
12298    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12299    cx.assert_editor_state("obj.aˇb");
12300    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12301
12302    // Backspace to delete "a" - dismisses menu.
12303    cx.update_editor(|editor, window, cx| {
12304        editor.backspace(&Backspace, window, cx);
12305    });
12306    cx.run_until_parked();
12307    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12308    cx.assert_editor_state("obj.ˇb");
12309    cx.update_editor(|editor, _, _| {
12310        assert_eq!(editor.context_menu_visible(), false);
12311    });
12312}
12313
12314#[gpui::test]
12315async fn test_word_completion(cx: &mut TestAppContext) {
12316    let lsp_fetch_timeout_ms = 10;
12317    init_test(cx, |language_settings| {
12318        language_settings.defaults.completions = Some(CompletionSettings {
12319            words: WordsCompletionMode::Fallback,
12320            lsp: true,
12321            lsp_fetch_timeout_ms: 10,
12322            lsp_insert_mode: LspInsertMode::Insert,
12323        });
12324    });
12325
12326    let mut cx = EditorLspTestContext::new_rust(
12327        lsp::ServerCapabilities {
12328            completion_provider: Some(lsp::CompletionOptions {
12329                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12330                ..lsp::CompletionOptions::default()
12331            }),
12332            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12333            ..lsp::ServerCapabilities::default()
12334        },
12335        cx,
12336    )
12337    .await;
12338
12339    let throttle_completions = Arc::new(AtomicBool::new(false));
12340
12341    let lsp_throttle_completions = throttle_completions.clone();
12342    let _completion_requests_handler =
12343        cx.lsp
12344            .server
12345            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12346                let lsp_throttle_completions = lsp_throttle_completions.clone();
12347                let cx = cx.clone();
12348                async move {
12349                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12350                        cx.background_executor()
12351                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12352                            .await;
12353                    }
12354                    Ok(Some(lsp::CompletionResponse::Array(vec![
12355                        lsp::CompletionItem {
12356                            label: "first".into(),
12357                            ..lsp::CompletionItem::default()
12358                        },
12359                        lsp::CompletionItem {
12360                            label: "last".into(),
12361                            ..lsp::CompletionItem::default()
12362                        },
12363                    ])))
12364                }
12365            });
12366
12367    cx.set_state(indoc! {"
12368        oneˇ
12369        two
12370        three
12371    "});
12372    cx.simulate_keystroke(".");
12373    cx.executor().run_until_parked();
12374    cx.condition(|editor, _| editor.context_menu_visible())
12375        .await;
12376    cx.update_editor(|editor, window, cx| {
12377        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12378        {
12379            assert_eq!(
12380                completion_menu_entries(&menu),
12381                &["first", "last"],
12382                "When LSP server is fast to reply, no fallback word completions are used"
12383            );
12384        } else {
12385            panic!("expected completion menu to be open");
12386        }
12387        editor.cancel(&Cancel, window, cx);
12388    });
12389    cx.executor().run_until_parked();
12390    cx.condition(|editor, _| !editor.context_menu_visible())
12391        .await;
12392
12393    throttle_completions.store(true, atomic::Ordering::Release);
12394    cx.simulate_keystroke(".");
12395    cx.executor()
12396        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12397    cx.executor().run_until_parked();
12398    cx.condition(|editor, _| editor.context_menu_visible())
12399        .await;
12400    cx.update_editor(|editor, _, _| {
12401        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12402        {
12403            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12404                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12405        } else {
12406            panic!("expected completion menu to be open");
12407        }
12408    });
12409}
12410
12411#[gpui::test]
12412async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12413    init_test(cx, |language_settings| {
12414        language_settings.defaults.completions = Some(CompletionSettings {
12415            words: WordsCompletionMode::Enabled,
12416            lsp: true,
12417            lsp_fetch_timeout_ms: 0,
12418            lsp_insert_mode: LspInsertMode::Insert,
12419        });
12420    });
12421
12422    let mut cx = EditorLspTestContext::new_rust(
12423        lsp::ServerCapabilities {
12424            completion_provider: Some(lsp::CompletionOptions {
12425                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12426                ..lsp::CompletionOptions::default()
12427            }),
12428            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12429            ..lsp::ServerCapabilities::default()
12430        },
12431        cx,
12432    )
12433    .await;
12434
12435    let _completion_requests_handler =
12436        cx.lsp
12437            .server
12438            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12439                Ok(Some(lsp::CompletionResponse::Array(vec![
12440                    lsp::CompletionItem {
12441                        label: "first".into(),
12442                        ..lsp::CompletionItem::default()
12443                    },
12444                    lsp::CompletionItem {
12445                        label: "last".into(),
12446                        ..lsp::CompletionItem::default()
12447                    },
12448                ])))
12449            });
12450
12451    cx.set_state(indoc! {"ˇ
12452        first
12453        last
12454        second
12455    "});
12456    cx.simulate_keystroke(".");
12457    cx.executor().run_until_parked();
12458    cx.condition(|editor, _| editor.context_menu_visible())
12459        .await;
12460    cx.update_editor(|editor, _, _| {
12461        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12462        {
12463            assert_eq!(
12464                completion_menu_entries(&menu),
12465                &["first", "last", "second"],
12466                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12467            );
12468        } else {
12469            panic!("expected completion menu to be open");
12470        }
12471    });
12472}
12473
12474#[gpui::test]
12475async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12476    init_test(cx, |language_settings| {
12477        language_settings.defaults.completions = Some(CompletionSettings {
12478            words: WordsCompletionMode::Disabled,
12479            lsp: true,
12480            lsp_fetch_timeout_ms: 0,
12481            lsp_insert_mode: LspInsertMode::Insert,
12482        });
12483    });
12484
12485    let mut cx = EditorLspTestContext::new_rust(
12486        lsp::ServerCapabilities {
12487            completion_provider: Some(lsp::CompletionOptions {
12488                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12489                ..lsp::CompletionOptions::default()
12490            }),
12491            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12492            ..lsp::ServerCapabilities::default()
12493        },
12494        cx,
12495    )
12496    .await;
12497
12498    let _completion_requests_handler =
12499        cx.lsp
12500            .server
12501            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12502                panic!("LSP completions should not be queried when dealing with word completions")
12503            });
12504
12505    cx.set_state(indoc! {"ˇ
12506        first
12507        last
12508        second
12509    "});
12510    cx.update_editor(|editor, window, cx| {
12511        editor.show_word_completions(&ShowWordCompletions, window, cx);
12512    });
12513    cx.executor().run_until_parked();
12514    cx.condition(|editor, _| editor.context_menu_visible())
12515        .await;
12516    cx.update_editor(|editor, _, _| {
12517        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12518        {
12519            assert_eq!(
12520                completion_menu_entries(&menu),
12521                &["first", "last", "second"],
12522                "`ShowWordCompletions` action should show word completions"
12523            );
12524        } else {
12525            panic!("expected completion menu to be open");
12526        }
12527    });
12528
12529    cx.simulate_keystroke("l");
12530    cx.executor().run_until_parked();
12531    cx.condition(|editor, _| editor.context_menu_visible())
12532        .await;
12533    cx.update_editor(|editor, _, _| {
12534        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12535        {
12536            assert_eq!(
12537                completion_menu_entries(&menu),
12538                &["last"],
12539                "After showing word completions, further editing should filter them and not query the LSP"
12540            );
12541        } else {
12542            panic!("expected completion menu to be open");
12543        }
12544    });
12545}
12546
12547#[gpui::test]
12548async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12549    init_test(cx, |language_settings| {
12550        language_settings.defaults.completions = Some(CompletionSettings {
12551            words: WordsCompletionMode::Fallback,
12552            lsp: false,
12553            lsp_fetch_timeout_ms: 0,
12554            lsp_insert_mode: LspInsertMode::Insert,
12555        });
12556    });
12557
12558    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12559
12560    cx.set_state(indoc! {"ˇ
12561        0_usize
12562        let
12563        33
12564        4.5f32
12565    "});
12566    cx.update_editor(|editor, window, cx| {
12567        editor.show_completions(&ShowCompletions::default(), window, cx);
12568    });
12569    cx.executor().run_until_parked();
12570    cx.condition(|editor, _| editor.context_menu_visible())
12571        .await;
12572    cx.update_editor(|editor, window, cx| {
12573        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12574        {
12575            assert_eq!(
12576                completion_menu_entries(&menu),
12577                &["let"],
12578                "With no digits in the completion query, no digits should be in the word completions"
12579            );
12580        } else {
12581            panic!("expected completion menu to be open");
12582        }
12583        editor.cancel(&Cancel, window, cx);
12584    });
12585
12586    cx.set_state(indoc! {"12587        0_usize
12588        let
12589        3
12590        33.35f32
12591    "});
12592    cx.update_editor(|editor, window, cx| {
12593        editor.show_completions(&ShowCompletions::default(), window, cx);
12594    });
12595    cx.executor().run_until_parked();
12596    cx.condition(|editor, _| editor.context_menu_visible())
12597        .await;
12598    cx.update_editor(|editor, _, _| {
12599        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12600        {
12601            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12602                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12603        } else {
12604            panic!("expected completion menu to be open");
12605        }
12606    });
12607}
12608
12609fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12610    let position = || lsp::Position {
12611        line: params.text_document_position.position.line,
12612        character: params.text_document_position.position.character,
12613    };
12614    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12615        range: lsp::Range {
12616            start: position(),
12617            end: position(),
12618        },
12619        new_text: text.to_string(),
12620    }))
12621}
12622
12623#[gpui::test]
12624async fn test_multiline_completion(cx: &mut TestAppContext) {
12625    init_test(cx, |_| {});
12626
12627    let fs = FakeFs::new(cx.executor());
12628    fs.insert_tree(
12629        path!("/a"),
12630        json!({
12631            "main.ts": "a",
12632        }),
12633    )
12634    .await;
12635
12636    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12637    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12638    let typescript_language = Arc::new(Language::new(
12639        LanguageConfig {
12640            name: "TypeScript".into(),
12641            matcher: LanguageMatcher {
12642                path_suffixes: vec!["ts".to_string()],
12643                ..LanguageMatcher::default()
12644            },
12645            line_comments: vec!["// ".into()],
12646            ..LanguageConfig::default()
12647        },
12648        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12649    ));
12650    language_registry.add(typescript_language.clone());
12651    let mut fake_servers = language_registry.register_fake_lsp(
12652        "TypeScript",
12653        FakeLspAdapter {
12654            capabilities: lsp::ServerCapabilities {
12655                completion_provider: Some(lsp::CompletionOptions {
12656                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12657                    ..lsp::CompletionOptions::default()
12658                }),
12659                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12660                ..lsp::ServerCapabilities::default()
12661            },
12662            // Emulate vtsls label generation
12663            label_for_completion: Some(Box::new(|item, _| {
12664                let text = if let Some(description) = item
12665                    .label_details
12666                    .as_ref()
12667                    .and_then(|label_details| label_details.description.as_ref())
12668                {
12669                    format!("{} {}", item.label, description)
12670                } else if let Some(detail) = &item.detail {
12671                    format!("{} {}", item.label, detail)
12672                } else {
12673                    item.label.clone()
12674                };
12675                let len = text.len();
12676                Some(language::CodeLabel {
12677                    text,
12678                    runs: Vec::new(),
12679                    filter_range: 0..len,
12680                })
12681            })),
12682            ..FakeLspAdapter::default()
12683        },
12684    );
12685    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12686    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12687    let worktree_id = workspace
12688        .update(cx, |workspace, _window, cx| {
12689            workspace.project().update(cx, |project, cx| {
12690                project.worktrees(cx).next().unwrap().read(cx).id()
12691            })
12692        })
12693        .unwrap();
12694    let _buffer = project
12695        .update(cx, |project, cx| {
12696            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12697        })
12698        .await
12699        .unwrap();
12700    let editor = workspace
12701        .update(cx, |workspace, window, cx| {
12702            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12703        })
12704        .unwrap()
12705        .await
12706        .unwrap()
12707        .downcast::<Editor>()
12708        .unwrap();
12709    let fake_server = fake_servers.next().await.unwrap();
12710
12711    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12712    let multiline_label_2 = "a\nb\nc\n";
12713    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12714    let multiline_description = "d\ne\nf\n";
12715    let multiline_detail_2 = "g\nh\ni\n";
12716
12717    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12718        move |params, _| async move {
12719            Ok(Some(lsp::CompletionResponse::Array(vec![
12720                lsp::CompletionItem {
12721                    label: multiline_label.to_string(),
12722                    text_edit: gen_text_edit(&params, "new_text_1"),
12723                    ..lsp::CompletionItem::default()
12724                },
12725                lsp::CompletionItem {
12726                    label: "single line label 1".to_string(),
12727                    detail: Some(multiline_detail.to_string()),
12728                    text_edit: gen_text_edit(&params, "new_text_2"),
12729                    ..lsp::CompletionItem::default()
12730                },
12731                lsp::CompletionItem {
12732                    label: "single line label 2".to_string(),
12733                    label_details: Some(lsp::CompletionItemLabelDetails {
12734                        description: Some(multiline_description.to_string()),
12735                        detail: None,
12736                    }),
12737                    text_edit: gen_text_edit(&params, "new_text_2"),
12738                    ..lsp::CompletionItem::default()
12739                },
12740                lsp::CompletionItem {
12741                    label: multiline_label_2.to_string(),
12742                    detail: Some(multiline_detail_2.to_string()),
12743                    text_edit: gen_text_edit(&params, "new_text_3"),
12744                    ..lsp::CompletionItem::default()
12745                },
12746                lsp::CompletionItem {
12747                    label: "Label with many     spaces and \t but without newlines".to_string(),
12748                    detail: Some(
12749                        "Details with many     spaces and \t but without newlines".to_string(),
12750                    ),
12751                    text_edit: gen_text_edit(&params, "new_text_4"),
12752                    ..lsp::CompletionItem::default()
12753                },
12754            ])))
12755        },
12756    );
12757
12758    editor.update_in(cx, |editor, window, cx| {
12759        cx.focus_self(window);
12760        editor.move_to_end(&MoveToEnd, window, cx);
12761        editor.handle_input(".", window, cx);
12762    });
12763    cx.run_until_parked();
12764    completion_handle.next().await.unwrap();
12765
12766    editor.update(cx, |editor, _| {
12767        assert!(editor.context_menu_visible());
12768        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12769        {
12770            let completion_labels = menu
12771                .completions
12772                .borrow()
12773                .iter()
12774                .map(|c| c.label.text.clone())
12775                .collect::<Vec<_>>();
12776            assert_eq!(
12777                completion_labels,
12778                &[
12779                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12780                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12781                    "single line label 2 d e f ",
12782                    "a b c g h i ",
12783                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12784                ],
12785                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12786            );
12787
12788            for completion in menu
12789                .completions
12790                .borrow()
12791                .iter() {
12792                    assert_eq!(
12793                        completion.label.filter_range,
12794                        0..completion.label.text.len(),
12795                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12796                    );
12797                }
12798        } else {
12799            panic!("expected completion menu to be open");
12800        }
12801    });
12802}
12803
12804#[gpui::test]
12805async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12806    init_test(cx, |_| {});
12807    let mut cx = EditorLspTestContext::new_rust(
12808        lsp::ServerCapabilities {
12809            completion_provider: Some(lsp::CompletionOptions {
12810                trigger_characters: Some(vec![".".to_string()]),
12811                ..Default::default()
12812            }),
12813            ..Default::default()
12814        },
12815        cx,
12816    )
12817    .await;
12818    cx.lsp
12819        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12820            Ok(Some(lsp::CompletionResponse::Array(vec![
12821                lsp::CompletionItem {
12822                    label: "first".into(),
12823                    ..Default::default()
12824                },
12825                lsp::CompletionItem {
12826                    label: "last".into(),
12827                    ..Default::default()
12828                },
12829            ])))
12830        });
12831    cx.set_state("variableˇ");
12832    cx.simulate_keystroke(".");
12833    cx.executor().run_until_parked();
12834
12835    cx.update_editor(|editor, _, _| {
12836        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12837        {
12838            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12839        } else {
12840            panic!("expected completion menu to be open");
12841        }
12842    });
12843
12844    cx.update_editor(|editor, window, cx| {
12845        editor.move_page_down(&MovePageDown::default(), window, cx);
12846        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12847        {
12848            assert!(
12849                menu.selected_item == 1,
12850                "expected PageDown to select the last item from the context menu"
12851            );
12852        } else {
12853            panic!("expected completion menu to stay open after PageDown");
12854        }
12855    });
12856
12857    cx.update_editor(|editor, window, cx| {
12858        editor.move_page_up(&MovePageUp::default(), window, cx);
12859        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12860        {
12861            assert!(
12862                menu.selected_item == 0,
12863                "expected PageUp to select the first item from the context menu"
12864            );
12865        } else {
12866            panic!("expected completion menu to stay open after PageUp");
12867        }
12868    });
12869}
12870
12871#[gpui::test]
12872async fn test_as_is_completions(cx: &mut TestAppContext) {
12873    init_test(cx, |_| {});
12874    let mut cx = EditorLspTestContext::new_rust(
12875        lsp::ServerCapabilities {
12876            completion_provider: Some(lsp::CompletionOptions {
12877                ..Default::default()
12878            }),
12879            ..Default::default()
12880        },
12881        cx,
12882    )
12883    .await;
12884    cx.lsp
12885        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12886            Ok(Some(lsp::CompletionResponse::Array(vec![
12887                lsp::CompletionItem {
12888                    label: "unsafe".into(),
12889                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12890                        range: lsp::Range {
12891                            start: lsp::Position {
12892                                line: 1,
12893                                character: 2,
12894                            },
12895                            end: lsp::Position {
12896                                line: 1,
12897                                character: 3,
12898                            },
12899                        },
12900                        new_text: "unsafe".to_string(),
12901                    })),
12902                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12903                    ..Default::default()
12904                },
12905            ])))
12906        });
12907    cx.set_state("fn a() {}\n");
12908    cx.executor().run_until_parked();
12909    cx.update_editor(|editor, window, cx| {
12910        editor.show_completions(
12911            &ShowCompletions {
12912                trigger: Some("\n".into()),
12913            },
12914            window,
12915            cx,
12916        );
12917    });
12918    cx.executor().run_until_parked();
12919
12920    cx.update_editor(|editor, window, cx| {
12921        editor.confirm_completion(&Default::default(), window, cx)
12922    });
12923    cx.executor().run_until_parked();
12924    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12925}
12926
12927#[gpui::test]
12928async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12929    init_test(cx, |_| {});
12930
12931    let mut cx = EditorLspTestContext::new_rust(
12932        lsp::ServerCapabilities {
12933            completion_provider: Some(lsp::CompletionOptions {
12934                trigger_characters: Some(vec![".".to_string()]),
12935                resolve_provider: Some(true),
12936                ..Default::default()
12937            }),
12938            ..Default::default()
12939        },
12940        cx,
12941    )
12942    .await;
12943
12944    cx.set_state("fn main() { let a = 2ˇ; }");
12945    cx.simulate_keystroke(".");
12946    let completion_item = lsp::CompletionItem {
12947        label: "Some".into(),
12948        kind: Some(lsp::CompletionItemKind::SNIPPET),
12949        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12950        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12951            kind: lsp::MarkupKind::Markdown,
12952            value: "```rust\nSome(2)\n```".to_string(),
12953        })),
12954        deprecated: Some(false),
12955        sort_text: Some("Some".to_string()),
12956        filter_text: Some("Some".to_string()),
12957        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12958        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12959            range: lsp::Range {
12960                start: lsp::Position {
12961                    line: 0,
12962                    character: 22,
12963                },
12964                end: lsp::Position {
12965                    line: 0,
12966                    character: 22,
12967                },
12968            },
12969            new_text: "Some(2)".to_string(),
12970        })),
12971        additional_text_edits: Some(vec![lsp::TextEdit {
12972            range: lsp::Range {
12973                start: lsp::Position {
12974                    line: 0,
12975                    character: 20,
12976                },
12977                end: lsp::Position {
12978                    line: 0,
12979                    character: 22,
12980                },
12981            },
12982            new_text: "".to_string(),
12983        }]),
12984        ..Default::default()
12985    };
12986
12987    let closure_completion_item = completion_item.clone();
12988    let counter = Arc::new(AtomicUsize::new(0));
12989    let counter_clone = counter.clone();
12990    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12991        let task_completion_item = closure_completion_item.clone();
12992        counter_clone.fetch_add(1, atomic::Ordering::Release);
12993        async move {
12994            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12995                is_incomplete: true,
12996                item_defaults: None,
12997                items: vec![task_completion_item],
12998            })))
12999        }
13000    });
13001
13002    cx.condition(|editor, _| editor.context_menu_visible())
13003        .await;
13004    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13005    assert!(request.next().await.is_some());
13006    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13007
13008    cx.simulate_keystrokes("S o m");
13009    cx.condition(|editor, _| editor.context_menu_visible())
13010        .await;
13011    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13012    assert!(request.next().await.is_some());
13013    assert!(request.next().await.is_some());
13014    assert!(request.next().await.is_some());
13015    request.close();
13016    assert!(request.next().await.is_none());
13017    assert_eq!(
13018        counter.load(atomic::Ordering::Acquire),
13019        4,
13020        "With the completions menu open, only one LSP request should happen per input"
13021    );
13022}
13023
13024#[gpui::test]
13025async fn test_toggle_comment(cx: &mut TestAppContext) {
13026    init_test(cx, |_| {});
13027    let mut cx = EditorTestContext::new(cx).await;
13028    let language = Arc::new(Language::new(
13029        LanguageConfig {
13030            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13031            ..Default::default()
13032        },
13033        Some(tree_sitter_rust::LANGUAGE.into()),
13034    ));
13035    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13036
13037    // If multiple selections intersect a line, the line is only toggled once.
13038    cx.set_state(indoc! {"
13039        fn a() {
13040            «//b();
13041            ˇ»// «c();
13042            //ˇ»  d();
13043        }
13044    "});
13045
13046    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13047
13048    cx.assert_editor_state(indoc! {"
13049        fn a() {
13050            «b();
13051            c();
13052            ˇ» d();
13053        }
13054    "});
13055
13056    // The comment prefix is inserted at the same column for every line in a
13057    // selection.
13058    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13059
13060    cx.assert_editor_state(indoc! {"
13061        fn a() {
13062            // «b();
13063            // c();
13064            ˇ»//  d();
13065        }
13066    "});
13067
13068    // If a selection ends at the beginning of a line, that line is not toggled.
13069    cx.set_selections_state(indoc! {"
13070        fn a() {
13071            // b();
13072            «// c();
13073        ˇ»    //  d();
13074        }
13075    "});
13076
13077    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13078
13079    cx.assert_editor_state(indoc! {"
13080        fn a() {
13081            // b();
13082            «c();
13083        ˇ»    //  d();
13084        }
13085    "});
13086
13087    // If a selection span a single line and is empty, the line is toggled.
13088    cx.set_state(indoc! {"
13089        fn a() {
13090            a();
13091            b();
13092        ˇ
13093        }
13094    "});
13095
13096    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13097
13098    cx.assert_editor_state(indoc! {"
13099        fn a() {
13100            a();
13101            b();
13102        //•ˇ
13103        }
13104    "});
13105
13106    // If a selection span multiple lines, empty lines are not toggled.
13107    cx.set_state(indoc! {"
13108        fn a() {
13109            «a();
13110
13111            c();ˇ»
13112        }
13113    "});
13114
13115    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13116
13117    cx.assert_editor_state(indoc! {"
13118        fn a() {
13119            // «a();
13120
13121            // c();ˇ»
13122        }
13123    "});
13124
13125    // If a selection includes multiple comment prefixes, all lines are uncommented.
13126    cx.set_state(indoc! {"
13127        fn a() {
13128            «// a();
13129            /// b();
13130            //! c();ˇ»
13131        }
13132    "});
13133
13134    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13135
13136    cx.assert_editor_state(indoc! {"
13137        fn a() {
13138            «a();
13139            b();
13140            c();ˇ»
13141        }
13142    "});
13143}
13144
13145#[gpui::test]
13146async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13147    init_test(cx, |_| {});
13148    let mut cx = EditorTestContext::new(cx).await;
13149    let language = Arc::new(Language::new(
13150        LanguageConfig {
13151            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13152            ..Default::default()
13153        },
13154        Some(tree_sitter_rust::LANGUAGE.into()),
13155    ));
13156    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13157
13158    let toggle_comments = &ToggleComments {
13159        advance_downwards: false,
13160        ignore_indent: true,
13161    };
13162
13163    // If multiple selections intersect a line, the line is only toggled once.
13164    cx.set_state(indoc! {"
13165        fn a() {
13166        //    «b();
13167        //    c();
13168        //    ˇ» d();
13169        }
13170    "});
13171
13172    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13173
13174    cx.assert_editor_state(indoc! {"
13175        fn a() {
13176            «b();
13177            c();
13178            ˇ» d();
13179        }
13180    "});
13181
13182    // The comment prefix is inserted at the beginning of each line
13183    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13184
13185    cx.assert_editor_state(indoc! {"
13186        fn a() {
13187        //    «b();
13188        //    c();
13189        //    ˇ» d();
13190        }
13191    "});
13192
13193    // If a selection ends at the beginning of a line, that line is not toggled.
13194    cx.set_selections_state(indoc! {"
13195        fn a() {
13196        //    b();
13197        //    «c();
13198        ˇ»//     d();
13199        }
13200    "});
13201
13202    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13203
13204    cx.assert_editor_state(indoc! {"
13205        fn a() {
13206        //    b();
13207            «c();
13208        ˇ»//     d();
13209        }
13210    "});
13211
13212    // If a selection span a single line and is empty, the line is toggled.
13213    cx.set_state(indoc! {"
13214        fn a() {
13215            a();
13216            b();
13217        ˇ
13218        }
13219    "});
13220
13221    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13222
13223    cx.assert_editor_state(indoc! {"
13224        fn a() {
13225            a();
13226            b();
13227        //ˇ
13228        }
13229    "});
13230
13231    // If a selection span multiple lines, empty lines are not toggled.
13232    cx.set_state(indoc! {"
13233        fn a() {
13234            «a();
13235
13236            c();ˇ»
13237        }
13238    "});
13239
13240    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13241
13242    cx.assert_editor_state(indoc! {"
13243        fn a() {
13244        //    «a();
13245
13246        //    c();ˇ»
13247        }
13248    "});
13249
13250    // If a selection includes multiple comment prefixes, all lines are uncommented.
13251    cx.set_state(indoc! {"
13252        fn a() {
13253        //    «a();
13254        ///    b();
13255        //!    c();ˇ»
13256        }
13257    "});
13258
13259    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13260
13261    cx.assert_editor_state(indoc! {"
13262        fn a() {
13263            «a();
13264            b();
13265            c();ˇ»
13266        }
13267    "});
13268}
13269
13270#[gpui::test]
13271async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13272    init_test(cx, |_| {});
13273
13274    let language = Arc::new(Language::new(
13275        LanguageConfig {
13276            line_comments: vec!["// ".into()],
13277            ..Default::default()
13278        },
13279        Some(tree_sitter_rust::LANGUAGE.into()),
13280    ));
13281
13282    let mut cx = EditorTestContext::new(cx).await;
13283
13284    cx.language_registry().add(language.clone());
13285    cx.update_buffer(|buffer, cx| {
13286        buffer.set_language(Some(language), cx);
13287    });
13288
13289    let toggle_comments = &ToggleComments {
13290        advance_downwards: true,
13291        ignore_indent: false,
13292    };
13293
13294    // Single cursor on one line -> advance
13295    // Cursor moves horizontally 3 characters as well on non-blank line
13296    cx.set_state(indoc!(
13297        "fn a() {
13298             ˇdog();
13299             cat();
13300        }"
13301    ));
13302    cx.update_editor(|editor, window, cx| {
13303        editor.toggle_comments(toggle_comments, window, cx);
13304    });
13305    cx.assert_editor_state(indoc!(
13306        "fn a() {
13307             // dog();
13308             catˇ();
13309        }"
13310    ));
13311
13312    // Single selection on one line -> don't advance
13313    cx.set_state(indoc!(
13314        "fn a() {
13315             «dog()ˇ»;
13316             cat();
13317        }"
13318    ));
13319    cx.update_editor(|editor, window, cx| {
13320        editor.toggle_comments(toggle_comments, window, cx);
13321    });
13322    cx.assert_editor_state(indoc!(
13323        "fn a() {
13324             // «dog()ˇ»;
13325             cat();
13326        }"
13327    ));
13328
13329    // Multiple cursors on one line -> advance
13330    cx.set_state(indoc!(
13331        "fn a() {
13332             ˇdˇog();
13333             cat();
13334        }"
13335    ));
13336    cx.update_editor(|editor, window, cx| {
13337        editor.toggle_comments(toggle_comments, window, cx);
13338    });
13339    cx.assert_editor_state(indoc!(
13340        "fn a() {
13341             // dog();
13342             catˇ(ˇ);
13343        }"
13344    ));
13345
13346    // Multiple cursors on one line, with selection -> don't advance
13347    cx.set_state(indoc!(
13348        "fn a() {
13349             ˇdˇog«()ˇ»;
13350             cat();
13351        }"
13352    ));
13353    cx.update_editor(|editor, window, cx| {
13354        editor.toggle_comments(toggle_comments, window, cx);
13355    });
13356    cx.assert_editor_state(indoc!(
13357        "fn a() {
13358             // ˇdˇog«()ˇ»;
13359             cat();
13360        }"
13361    ));
13362
13363    // Single cursor on one line -> advance
13364    // Cursor moves to column 0 on blank line
13365    cx.set_state(indoc!(
13366        "fn a() {
13367             ˇdog();
13368
13369             cat();
13370        }"
13371    ));
13372    cx.update_editor(|editor, window, cx| {
13373        editor.toggle_comments(toggle_comments, window, cx);
13374    });
13375    cx.assert_editor_state(indoc!(
13376        "fn a() {
13377             // dog();
13378        ˇ
13379             cat();
13380        }"
13381    ));
13382
13383    // Single cursor on one line -> advance
13384    // Cursor starts and ends at column 0
13385    cx.set_state(indoc!(
13386        "fn a() {
13387         ˇ    dog();
13388             cat();
13389        }"
13390    ));
13391    cx.update_editor(|editor, window, cx| {
13392        editor.toggle_comments(toggle_comments, window, cx);
13393    });
13394    cx.assert_editor_state(indoc!(
13395        "fn a() {
13396             // dog();
13397         ˇ    cat();
13398        }"
13399    ));
13400}
13401
13402#[gpui::test]
13403async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13404    init_test(cx, |_| {});
13405
13406    let mut cx = EditorTestContext::new(cx).await;
13407
13408    let html_language = Arc::new(
13409        Language::new(
13410            LanguageConfig {
13411                name: "HTML".into(),
13412                block_comment: Some(("<!-- ".into(), " -->".into())),
13413                ..Default::default()
13414            },
13415            Some(tree_sitter_html::LANGUAGE.into()),
13416        )
13417        .with_injection_query(
13418            r#"
13419            (script_element
13420                (raw_text) @injection.content
13421                (#set! injection.language "javascript"))
13422            "#,
13423        )
13424        .unwrap(),
13425    );
13426
13427    let javascript_language = Arc::new(Language::new(
13428        LanguageConfig {
13429            name: "JavaScript".into(),
13430            line_comments: vec!["// ".into()],
13431            ..Default::default()
13432        },
13433        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13434    ));
13435
13436    cx.language_registry().add(html_language.clone());
13437    cx.language_registry().add(javascript_language.clone());
13438    cx.update_buffer(|buffer, cx| {
13439        buffer.set_language(Some(html_language), cx);
13440    });
13441
13442    // Toggle comments for empty selections
13443    cx.set_state(
13444        &r#"
13445            <p>A</p>ˇ
13446            <p>B</p>ˇ
13447            <p>C</p>ˇ
13448        "#
13449        .unindent(),
13450    );
13451    cx.update_editor(|editor, window, cx| {
13452        editor.toggle_comments(&ToggleComments::default(), window, cx)
13453    });
13454    cx.assert_editor_state(
13455        &r#"
13456            <!-- <p>A</p>ˇ -->
13457            <!-- <p>B</p>ˇ -->
13458            <!-- <p>C</p>ˇ -->
13459        "#
13460        .unindent(),
13461    );
13462    cx.update_editor(|editor, window, cx| {
13463        editor.toggle_comments(&ToggleComments::default(), window, cx)
13464    });
13465    cx.assert_editor_state(
13466        &r#"
13467            <p>A</p>ˇ
13468            <p>B</p>ˇ
13469            <p>C</p>ˇ
13470        "#
13471        .unindent(),
13472    );
13473
13474    // Toggle comments for mixture of empty and non-empty selections, where
13475    // multiple selections occupy a given line.
13476    cx.set_state(
13477        &r#"
13478            <p>A«</p>
13479            <p>ˇ»B</p>ˇ
13480            <p>C«</p>
13481            <p>ˇ»D</p>ˇ
13482        "#
13483        .unindent(),
13484    );
13485
13486    cx.update_editor(|editor, window, cx| {
13487        editor.toggle_comments(&ToggleComments::default(), window, cx)
13488    });
13489    cx.assert_editor_state(
13490        &r#"
13491            <!-- <p>A«</p>
13492            <p>ˇ»B</p>ˇ -->
13493            <!-- <p>C«</p>
13494            <p>ˇ»D</p>ˇ -->
13495        "#
13496        .unindent(),
13497    );
13498    cx.update_editor(|editor, window, cx| {
13499        editor.toggle_comments(&ToggleComments::default(), window, cx)
13500    });
13501    cx.assert_editor_state(
13502        &r#"
13503            <p>A«</p>
13504            <p>ˇ»B</p>ˇ
13505            <p>C«</p>
13506            <p>ˇ»D</p>ˇ
13507        "#
13508        .unindent(),
13509    );
13510
13511    // Toggle comments when different languages are active for different
13512    // selections.
13513    cx.set_state(
13514        &r#"
13515            ˇ<script>
13516                ˇvar x = new Y();
13517            ˇ</script>
13518        "#
13519        .unindent(),
13520    );
13521    cx.executor().run_until_parked();
13522    cx.update_editor(|editor, window, cx| {
13523        editor.toggle_comments(&ToggleComments::default(), window, cx)
13524    });
13525    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13526    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13527    cx.assert_editor_state(
13528        &r#"
13529            <!-- ˇ<script> -->
13530                // ˇvar x = new Y();
13531            <!-- ˇ</script> -->
13532        "#
13533        .unindent(),
13534    );
13535}
13536
13537#[gpui::test]
13538fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13539    init_test(cx, |_| {});
13540
13541    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13542    let multibuffer = cx.new(|cx| {
13543        let mut multibuffer = MultiBuffer::new(ReadWrite);
13544        multibuffer.push_excerpts(
13545            buffer.clone(),
13546            [
13547                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13548                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13549            ],
13550            cx,
13551        );
13552        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13553        multibuffer
13554    });
13555
13556    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13557    editor.update_in(cx, |editor, window, cx| {
13558        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13560            s.select_ranges([
13561                Point::new(0, 0)..Point::new(0, 0),
13562                Point::new(1, 0)..Point::new(1, 0),
13563            ])
13564        });
13565
13566        editor.handle_input("X", window, cx);
13567        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13568        assert_eq!(
13569            editor.selections.ranges(cx),
13570            [
13571                Point::new(0, 1)..Point::new(0, 1),
13572                Point::new(1, 1)..Point::new(1, 1),
13573            ]
13574        );
13575
13576        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13577        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13578            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13579        });
13580        editor.backspace(&Default::default(), window, cx);
13581        assert_eq!(editor.text(cx), "Xa\nbbb");
13582        assert_eq!(
13583            editor.selections.ranges(cx),
13584            [Point::new(1, 0)..Point::new(1, 0)]
13585        );
13586
13587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13588            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13589        });
13590        editor.backspace(&Default::default(), window, cx);
13591        assert_eq!(editor.text(cx), "X\nbb");
13592        assert_eq!(
13593            editor.selections.ranges(cx),
13594            [Point::new(0, 1)..Point::new(0, 1)]
13595        );
13596    });
13597}
13598
13599#[gpui::test]
13600fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13601    init_test(cx, |_| {});
13602
13603    let markers = vec![('[', ']').into(), ('(', ')').into()];
13604    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13605        indoc! {"
13606            [aaaa
13607            (bbbb]
13608            cccc)",
13609        },
13610        markers.clone(),
13611    );
13612    let excerpt_ranges = markers.into_iter().map(|marker| {
13613        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13614        ExcerptRange::new(context.clone())
13615    });
13616    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13617    let multibuffer = cx.new(|cx| {
13618        let mut multibuffer = MultiBuffer::new(ReadWrite);
13619        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13620        multibuffer
13621    });
13622
13623    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13624    editor.update_in(cx, |editor, window, cx| {
13625        let (expected_text, selection_ranges) = marked_text_ranges(
13626            indoc! {"
13627                aaaa
13628                bˇbbb
13629                bˇbbˇb
13630                cccc"
13631            },
13632            true,
13633        );
13634        assert_eq!(editor.text(cx), expected_text);
13635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13636            s.select_ranges(selection_ranges)
13637        });
13638
13639        editor.handle_input("X", window, cx);
13640
13641        let (expected_text, expected_selections) = marked_text_ranges(
13642            indoc! {"
13643                aaaa
13644                bXˇbbXb
13645                bXˇbbXˇb
13646                cccc"
13647            },
13648            false,
13649        );
13650        assert_eq!(editor.text(cx), expected_text);
13651        assert_eq!(editor.selections.ranges(cx), expected_selections);
13652
13653        editor.newline(&Newline, window, cx);
13654        let (expected_text, expected_selections) = marked_text_ranges(
13655            indoc! {"
13656                aaaa
13657                bX
13658                ˇbbX
13659                b
13660                bX
13661                ˇbbX
13662                ˇb
13663                cccc"
13664            },
13665            false,
13666        );
13667        assert_eq!(editor.text(cx), expected_text);
13668        assert_eq!(editor.selections.ranges(cx), expected_selections);
13669    });
13670}
13671
13672#[gpui::test]
13673fn test_refresh_selections(cx: &mut TestAppContext) {
13674    init_test(cx, |_| {});
13675
13676    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13677    let mut excerpt1_id = None;
13678    let multibuffer = cx.new(|cx| {
13679        let mut multibuffer = MultiBuffer::new(ReadWrite);
13680        excerpt1_id = multibuffer
13681            .push_excerpts(
13682                buffer.clone(),
13683                [
13684                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13685                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13686                ],
13687                cx,
13688            )
13689            .into_iter()
13690            .next();
13691        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13692        multibuffer
13693    });
13694
13695    let editor = cx.add_window(|window, cx| {
13696        let mut editor = build_editor(multibuffer.clone(), window, cx);
13697        let snapshot = editor.snapshot(window, cx);
13698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13699            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13700        });
13701        editor.begin_selection(
13702            Point::new(2, 1).to_display_point(&snapshot),
13703            true,
13704            1,
13705            window,
13706            cx,
13707        );
13708        assert_eq!(
13709            editor.selections.ranges(cx),
13710            [
13711                Point::new(1, 3)..Point::new(1, 3),
13712                Point::new(2, 1)..Point::new(2, 1),
13713            ]
13714        );
13715        editor
13716    });
13717
13718    // Refreshing selections is a no-op when excerpts haven't changed.
13719    _ = editor.update(cx, |editor, window, cx| {
13720        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13721        assert_eq!(
13722            editor.selections.ranges(cx),
13723            [
13724                Point::new(1, 3)..Point::new(1, 3),
13725                Point::new(2, 1)..Point::new(2, 1),
13726            ]
13727        );
13728    });
13729
13730    multibuffer.update(cx, |multibuffer, cx| {
13731        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13732    });
13733    _ = editor.update(cx, |editor, window, cx| {
13734        // Removing an excerpt causes the first selection to become degenerate.
13735        assert_eq!(
13736            editor.selections.ranges(cx),
13737            [
13738                Point::new(0, 0)..Point::new(0, 0),
13739                Point::new(0, 1)..Point::new(0, 1)
13740            ]
13741        );
13742
13743        // Refreshing selections will relocate the first selection to the original buffer
13744        // location.
13745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13746        assert_eq!(
13747            editor.selections.ranges(cx),
13748            [
13749                Point::new(0, 1)..Point::new(0, 1),
13750                Point::new(0, 3)..Point::new(0, 3)
13751            ]
13752        );
13753        assert!(editor.selections.pending_anchor().is_some());
13754    });
13755}
13756
13757#[gpui::test]
13758fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13759    init_test(cx, |_| {});
13760
13761    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13762    let mut excerpt1_id = None;
13763    let multibuffer = cx.new(|cx| {
13764        let mut multibuffer = MultiBuffer::new(ReadWrite);
13765        excerpt1_id = multibuffer
13766            .push_excerpts(
13767                buffer.clone(),
13768                [
13769                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13770                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13771                ],
13772                cx,
13773            )
13774            .into_iter()
13775            .next();
13776        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13777        multibuffer
13778    });
13779
13780    let editor = cx.add_window(|window, cx| {
13781        let mut editor = build_editor(multibuffer.clone(), window, cx);
13782        let snapshot = editor.snapshot(window, cx);
13783        editor.begin_selection(
13784            Point::new(1, 3).to_display_point(&snapshot),
13785            false,
13786            1,
13787            window,
13788            cx,
13789        );
13790        assert_eq!(
13791            editor.selections.ranges(cx),
13792            [Point::new(1, 3)..Point::new(1, 3)]
13793        );
13794        editor
13795    });
13796
13797    multibuffer.update(cx, |multibuffer, cx| {
13798        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13799    });
13800    _ = editor.update(cx, |editor, window, cx| {
13801        assert_eq!(
13802            editor.selections.ranges(cx),
13803            [Point::new(0, 0)..Point::new(0, 0)]
13804        );
13805
13806        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13808        assert_eq!(
13809            editor.selections.ranges(cx),
13810            [Point::new(0, 3)..Point::new(0, 3)]
13811        );
13812        assert!(editor.selections.pending_anchor().is_some());
13813    });
13814}
13815
13816#[gpui::test]
13817async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13818    init_test(cx, |_| {});
13819
13820    let language = Arc::new(
13821        Language::new(
13822            LanguageConfig {
13823                brackets: BracketPairConfig {
13824                    pairs: vec![
13825                        BracketPair {
13826                            start: "{".to_string(),
13827                            end: "}".to_string(),
13828                            close: true,
13829                            surround: true,
13830                            newline: true,
13831                        },
13832                        BracketPair {
13833                            start: "/* ".to_string(),
13834                            end: " */".to_string(),
13835                            close: true,
13836                            surround: true,
13837                            newline: true,
13838                        },
13839                    ],
13840                    ..Default::default()
13841                },
13842                ..Default::default()
13843            },
13844            Some(tree_sitter_rust::LANGUAGE.into()),
13845        )
13846        .with_indents_query("")
13847        .unwrap(),
13848    );
13849
13850    let text = concat!(
13851        "{   }\n",     //
13852        "  x\n",       //
13853        "  /*   */\n", //
13854        "x\n",         //
13855        "{{} }\n",     //
13856    );
13857
13858    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13859    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13860    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13861    editor
13862        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13863        .await;
13864
13865    editor.update_in(cx, |editor, window, cx| {
13866        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13867            s.select_display_ranges([
13868                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13869                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13870                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13871            ])
13872        });
13873        editor.newline(&Newline, window, cx);
13874
13875        assert_eq!(
13876            editor.buffer().read(cx).read(cx).text(),
13877            concat!(
13878                "{ \n",    // Suppress rustfmt
13879                "\n",      //
13880                "}\n",     //
13881                "  x\n",   //
13882                "  /* \n", //
13883                "  \n",    //
13884                "  */\n",  //
13885                "x\n",     //
13886                "{{} \n",  //
13887                "}\n",     //
13888            )
13889        );
13890    });
13891}
13892
13893#[gpui::test]
13894fn test_highlighted_ranges(cx: &mut TestAppContext) {
13895    init_test(cx, |_| {});
13896
13897    let editor = cx.add_window(|window, cx| {
13898        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13899        build_editor(buffer.clone(), window, cx)
13900    });
13901
13902    _ = editor.update(cx, |editor, window, cx| {
13903        struct Type1;
13904        struct Type2;
13905
13906        let buffer = editor.buffer.read(cx).snapshot(cx);
13907
13908        let anchor_range =
13909            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13910
13911        editor.highlight_background::<Type1>(
13912            &[
13913                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13914                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13915                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13916                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13917            ],
13918            |_| Hsla::red(),
13919            cx,
13920        );
13921        editor.highlight_background::<Type2>(
13922            &[
13923                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13924                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13925                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13926                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13927            ],
13928            |_| Hsla::green(),
13929            cx,
13930        );
13931
13932        let snapshot = editor.snapshot(window, cx);
13933        let mut highlighted_ranges = editor.background_highlights_in_range(
13934            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13935            &snapshot,
13936            cx.theme(),
13937        );
13938        // Enforce a consistent ordering based on color without relying on the ordering of the
13939        // highlight's `TypeId` which is non-executor.
13940        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13941        assert_eq!(
13942            highlighted_ranges,
13943            &[
13944                (
13945                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13946                    Hsla::red(),
13947                ),
13948                (
13949                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13950                    Hsla::red(),
13951                ),
13952                (
13953                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13954                    Hsla::green(),
13955                ),
13956                (
13957                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13958                    Hsla::green(),
13959                ),
13960            ]
13961        );
13962        assert_eq!(
13963            editor.background_highlights_in_range(
13964                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13965                &snapshot,
13966                cx.theme(),
13967            ),
13968            &[(
13969                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13970                Hsla::red(),
13971            )]
13972        );
13973    });
13974}
13975
13976#[gpui::test]
13977async fn test_following(cx: &mut TestAppContext) {
13978    init_test(cx, |_| {});
13979
13980    let fs = FakeFs::new(cx.executor());
13981    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13982
13983    let buffer = project.update(cx, |project, cx| {
13984        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13985        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13986    });
13987    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13988    let follower = cx.update(|cx| {
13989        cx.open_window(
13990            WindowOptions {
13991                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13992                    gpui::Point::new(px(0.), px(0.)),
13993                    gpui::Point::new(px(10.), px(80.)),
13994                ))),
13995                ..Default::default()
13996            },
13997            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13998        )
13999        .unwrap()
14000    });
14001
14002    let is_still_following = Rc::new(RefCell::new(true));
14003    let follower_edit_event_count = Rc::new(RefCell::new(0));
14004    let pending_update = Rc::new(RefCell::new(None));
14005    let leader_entity = leader.root(cx).unwrap();
14006    let follower_entity = follower.root(cx).unwrap();
14007    _ = follower.update(cx, {
14008        let update = pending_update.clone();
14009        let is_still_following = is_still_following.clone();
14010        let follower_edit_event_count = follower_edit_event_count.clone();
14011        |_, window, cx| {
14012            cx.subscribe_in(
14013                &leader_entity,
14014                window,
14015                move |_, leader, event, window, cx| {
14016                    leader.read(cx).add_event_to_update_proto(
14017                        event,
14018                        &mut update.borrow_mut(),
14019                        window,
14020                        cx,
14021                    );
14022                },
14023            )
14024            .detach();
14025
14026            cx.subscribe_in(
14027                &follower_entity,
14028                window,
14029                move |_, _, event: &EditorEvent, _window, _cx| {
14030                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14031                        *is_still_following.borrow_mut() = false;
14032                    }
14033
14034                    if let EditorEvent::BufferEdited = event {
14035                        *follower_edit_event_count.borrow_mut() += 1;
14036                    }
14037                },
14038            )
14039            .detach();
14040        }
14041    });
14042
14043    // Update the selections only
14044    _ = leader.update(cx, |leader, window, cx| {
14045        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14046            s.select_ranges([1..1])
14047        });
14048    });
14049    follower
14050        .update(cx, |follower, window, cx| {
14051            follower.apply_update_proto(
14052                &project,
14053                pending_update.borrow_mut().take().unwrap(),
14054                window,
14055                cx,
14056            )
14057        })
14058        .unwrap()
14059        .await
14060        .unwrap();
14061    _ = follower.update(cx, |follower, _, cx| {
14062        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14063    });
14064    assert!(*is_still_following.borrow());
14065    assert_eq!(*follower_edit_event_count.borrow(), 0);
14066
14067    // Update the scroll position only
14068    _ = leader.update(cx, |leader, window, cx| {
14069        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14070    });
14071    follower
14072        .update(cx, |follower, window, cx| {
14073            follower.apply_update_proto(
14074                &project,
14075                pending_update.borrow_mut().take().unwrap(),
14076                window,
14077                cx,
14078            )
14079        })
14080        .unwrap()
14081        .await
14082        .unwrap();
14083    assert_eq!(
14084        follower
14085            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14086            .unwrap(),
14087        gpui::Point::new(1.5, 3.5)
14088    );
14089    assert!(*is_still_following.borrow());
14090    assert_eq!(*follower_edit_event_count.borrow(), 0);
14091
14092    // Update the selections and scroll position. The follower's scroll position is updated
14093    // via autoscroll, not via the leader's exact scroll position.
14094    _ = leader.update(cx, |leader, window, cx| {
14095        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14096            s.select_ranges([0..0])
14097        });
14098        leader.request_autoscroll(Autoscroll::newest(), cx);
14099        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14100    });
14101    follower
14102        .update(cx, |follower, window, cx| {
14103            follower.apply_update_proto(
14104                &project,
14105                pending_update.borrow_mut().take().unwrap(),
14106                window,
14107                cx,
14108            )
14109        })
14110        .unwrap()
14111        .await
14112        .unwrap();
14113    _ = follower.update(cx, |follower, _, cx| {
14114        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14115        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14116    });
14117    assert!(*is_still_following.borrow());
14118
14119    // Creating a pending selection that precedes another selection
14120    _ = leader.update(cx, |leader, window, cx| {
14121        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14122            s.select_ranges([1..1])
14123        });
14124        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14125    });
14126    follower
14127        .update(cx, |follower, window, cx| {
14128            follower.apply_update_proto(
14129                &project,
14130                pending_update.borrow_mut().take().unwrap(),
14131                window,
14132                cx,
14133            )
14134        })
14135        .unwrap()
14136        .await
14137        .unwrap();
14138    _ = follower.update(cx, |follower, _, cx| {
14139        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14140    });
14141    assert!(*is_still_following.borrow());
14142
14143    // Extend the pending selection so that it surrounds another selection
14144    _ = leader.update(cx, |leader, window, cx| {
14145        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14146    });
14147    follower
14148        .update(cx, |follower, window, cx| {
14149            follower.apply_update_proto(
14150                &project,
14151                pending_update.borrow_mut().take().unwrap(),
14152                window,
14153                cx,
14154            )
14155        })
14156        .unwrap()
14157        .await
14158        .unwrap();
14159    _ = follower.update(cx, |follower, _, cx| {
14160        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14161    });
14162
14163    // Scrolling locally breaks the follow
14164    _ = follower.update(cx, |follower, window, cx| {
14165        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14166        follower.set_scroll_anchor(
14167            ScrollAnchor {
14168                anchor: top_anchor,
14169                offset: gpui::Point::new(0.0, 0.5),
14170            },
14171            window,
14172            cx,
14173        );
14174    });
14175    assert!(!(*is_still_following.borrow()));
14176}
14177
14178#[gpui::test]
14179async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14180    init_test(cx, |_| {});
14181
14182    let fs = FakeFs::new(cx.executor());
14183    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14184    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14185    let pane = workspace
14186        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14187        .unwrap();
14188
14189    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14190
14191    let leader = pane.update_in(cx, |_, window, cx| {
14192        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14193        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14194    });
14195
14196    // Start following the editor when it has no excerpts.
14197    let mut state_message =
14198        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14199    let workspace_entity = workspace.root(cx).unwrap();
14200    let follower_1 = cx
14201        .update_window(*workspace.deref(), |_, window, cx| {
14202            Editor::from_state_proto(
14203                workspace_entity,
14204                ViewId {
14205                    creator: CollaboratorId::PeerId(PeerId::default()),
14206                    id: 0,
14207                },
14208                &mut state_message,
14209                window,
14210                cx,
14211            )
14212        })
14213        .unwrap()
14214        .unwrap()
14215        .await
14216        .unwrap();
14217
14218    let update_message = Rc::new(RefCell::new(None));
14219    follower_1.update_in(cx, {
14220        let update = update_message.clone();
14221        |_, window, cx| {
14222            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14223                leader.read(cx).add_event_to_update_proto(
14224                    event,
14225                    &mut update.borrow_mut(),
14226                    window,
14227                    cx,
14228                );
14229            })
14230            .detach();
14231        }
14232    });
14233
14234    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14235        (
14236            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14237            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14238        )
14239    });
14240
14241    // Insert some excerpts.
14242    leader.update(cx, |leader, cx| {
14243        leader.buffer.update(cx, |multibuffer, cx| {
14244            multibuffer.set_excerpts_for_path(
14245                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14246                buffer_1.clone(),
14247                vec![
14248                    Point::row_range(0..3),
14249                    Point::row_range(1..6),
14250                    Point::row_range(12..15),
14251                ],
14252                0,
14253                cx,
14254            );
14255            multibuffer.set_excerpts_for_path(
14256                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14257                buffer_2.clone(),
14258                vec![Point::row_range(0..6), Point::row_range(8..12)],
14259                0,
14260                cx,
14261            );
14262        });
14263    });
14264
14265    // Apply the update of adding the excerpts.
14266    follower_1
14267        .update_in(cx, |follower, window, cx| {
14268            follower.apply_update_proto(
14269                &project,
14270                update_message.borrow().clone().unwrap(),
14271                window,
14272                cx,
14273            )
14274        })
14275        .await
14276        .unwrap();
14277    assert_eq!(
14278        follower_1.update(cx, |editor, cx| editor.text(cx)),
14279        leader.update(cx, |editor, cx| editor.text(cx))
14280    );
14281    update_message.borrow_mut().take();
14282
14283    // Start following separately after it already has excerpts.
14284    let mut state_message =
14285        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14286    let workspace_entity = workspace.root(cx).unwrap();
14287    let follower_2 = cx
14288        .update_window(*workspace.deref(), |_, window, cx| {
14289            Editor::from_state_proto(
14290                workspace_entity,
14291                ViewId {
14292                    creator: CollaboratorId::PeerId(PeerId::default()),
14293                    id: 0,
14294                },
14295                &mut state_message,
14296                window,
14297                cx,
14298            )
14299        })
14300        .unwrap()
14301        .unwrap()
14302        .await
14303        .unwrap();
14304    assert_eq!(
14305        follower_2.update(cx, |editor, cx| editor.text(cx)),
14306        leader.update(cx, |editor, cx| editor.text(cx))
14307    );
14308
14309    // Remove some excerpts.
14310    leader.update(cx, |leader, cx| {
14311        leader.buffer.update(cx, |multibuffer, cx| {
14312            let excerpt_ids = multibuffer.excerpt_ids();
14313            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14314            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14315        });
14316    });
14317
14318    // Apply the update of removing the excerpts.
14319    follower_1
14320        .update_in(cx, |follower, window, cx| {
14321            follower.apply_update_proto(
14322                &project,
14323                update_message.borrow().clone().unwrap(),
14324                window,
14325                cx,
14326            )
14327        })
14328        .await
14329        .unwrap();
14330    follower_2
14331        .update_in(cx, |follower, window, cx| {
14332            follower.apply_update_proto(
14333                &project,
14334                update_message.borrow().clone().unwrap(),
14335                window,
14336                cx,
14337            )
14338        })
14339        .await
14340        .unwrap();
14341    update_message.borrow_mut().take();
14342    assert_eq!(
14343        follower_1.update(cx, |editor, cx| editor.text(cx)),
14344        leader.update(cx, |editor, cx| editor.text(cx))
14345    );
14346}
14347
14348#[gpui::test]
14349async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14350    init_test(cx, |_| {});
14351
14352    let mut cx = EditorTestContext::new(cx).await;
14353    let lsp_store =
14354        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14355
14356    cx.set_state(indoc! {"
14357        ˇfn func(abc def: i32) -> u32 {
14358        }
14359    "});
14360
14361    cx.update(|_, cx| {
14362        lsp_store.update(cx, |lsp_store, cx| {
14363            lsp_store
14364                .update_diagnostics(
14365                    LanguageServerId(0),
14366                    lsp::PublishDiagnosticsParams {
14367                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14368                        version: None,
14369                        diagnostics: vec![
14370                            lsp::Diagnostic {
14371                                range: lsp::Range::new(
14372                                    lsp::Position::new(0, 11),
14373                                    lsp::Position::new(0, 12),
14374                                ),
14375                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14376                                ..Default::default()
14377                            },
14378                            lsp::Diagnostic {
14379                                range: lsp::Range::new(
14380                                    lsp::Position::new(0, 12),
14381                                    lsp::Position::new(0, 15),
14382                                ),
14383                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14384                                ..Default::default()
14385                            },
14386                            lsp::Diagnostic {
14387                                range: lsp::Range::new(
14388                                    lsp::Position::new(0, 25),
14389                                    lsp::Position::new(0, 28),
14390                                ),
14391                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14392                                ..Default::default()
14393                            },
14394                        ],
14395                    },
14396                    None,
14397                    DiagnosticSourceKind::Pushed,
14398                    &[],
14399                    cx,
14400                )
14401                .unwrap()
14402        });
14403    });
14404
14405    executor.run_until_parked();
14406
14407    cx.update_editor(|editor, window, cx| {
14408        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14409    });
14410
14411    cx.assert_editor_state(indoc! {"
14412        fn func(abc def: i32) -> ˇu32 {
14413        }
14414    "});
14415
14416    cx.update_editor(|editor, window, cx| {
14417        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14418    });
14419
14420    cx.assert_editor_state(indoc! {"
14421        fn func(abc ˇdef: i32) -> u32 {
14422        }
14423    "});
14424
14425    cx.update_editor(|editor, window, cx| {
14426        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14427    });
14428
14429    cx.assert_editor_state(indoc! {"
14430        fn func(abcˇ def: i32) -> u32 {
14431        }
14432    "});
14433
14434    cx.update_editor(|editor, window, cx| {
14435        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14436    });
14437
14438    cx.assert_editor_state(indoc! {"
14439        fn func(abc def: i32) -> ˇu32 {
14440        }
14441    "});
14442}
14443
14444#[gpui::test]
14445async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14446    init_test(cx, |_| {});
14447
14448    let mut cx = EditorTestContext::new(cx).await;
14449
14450    let diff_base = r#"
14451        use some::mod;
14452
14453        const A: u32 = 42;
14454
14455        fn main() {
14456            println!("hello");
14457
14458            println!("world");
14459        }
14460        "#
14461    .unindent();
14462
14463    // Edits are modified, removed, modified, added
14464    cx.set_state(
14465        &r#"
14466        use some::modified;
14467
14468        ˇ
14469        fn main() {
14470            println!("hello there");
14471
14472            println!("around the");
14473            println!("world");
14474        }
14475        "#
14476        .unindent(),
14477    );
14478
14479    cx.set_head_text(&diff_base);
14480    executor.run_until_parked();
14481
14482    cx.update_editor(|editor, window, cx| {
14483        //Wrap around the bottom of the buffer
14484        for _ in 0..3 {
14485            editor.go_to_next_hunk(&GoToHunk, window, cx);
14486        }
14487    });
14488
14489    cx.assert_editor_state(
14490        &r#"
14491        ˇuse some::modified;
14492
14493
14494        fn main() {
14495            println!("hello there");
14496
14497            println!("around the");
14498            println!("world");
14499        }
14500        "#
14501        .unindent(),
14502    );
14503
14504    cx.update_editor(|editor, window, cx| {
14505        //Wrap around the top of the buffer
14506        for _ in 0..2 {
14507            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14508        }
14509    });
14510
14511    cx.assert_editor_state(
14512        &r#"
14513        use some::modified;
14514
14515
14516        fn main() {
14517        ˇ    println!("hello there");
14518
14519            println!("around the");
14520            println!("world");
14521        }
14522        "#
14523        .unindent(),
14524    );
14525
14526    cx.update_editor(|editor, window, cx| {
14527        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14528    });
14529
14530    cx.assert_editor_state(
14531        &r#"
14532        use some::modified;
14533
14534        ˇ
14535        fn main() {
14536            println!("hello there");
14537
14538            println!("around the");
14539            println!("world");
14540        }
14541        "#
14542        .unindent(),
14543    );
14544
14545    cx.update_editor(|editor, window, cx| {
14546        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14547    });
14548
14549    cx.assert_editor_state(
14550        &r#"
14551        ˇuse some::modified;
14552
14553
14554        fn main() {
14555            println!("hello there");
14556
14557            println!("around the");
14558            println!("world");
14559        }
14560        "#
14561        .unindent(),
14562    );
14563
14564    cx.update_editor(|editor, window, cx| {
14565        for _ in 0..2 {
14566            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14567        }
14568    });
14569
14570    cx.assert_editor_state(
14571        &r#"
14572        use some::modified;
14573
14574
14575        fn main() {
14576        ˇ    println!("hello there");
14577
14578            println!("around the");
14579            println!("world");
14580        }
14581        "#
14582        .unindent(),
14583    );
14584
14585    cx.update_editor(|editor, window, cx| {
14586        editor.fold(&Fold, window, cx);
14587    });
14588
14589    cx.update_editor(|editor, window, cx| {
14590        editor.go_to_next_hunk(&GoToHunk, window, cx);
14591    });
14592
14593    cx.assert_editor_state(
14594        &r#"
14595        ˇuse some::modified;
14596
14597
14598        fn main() {
14599            println!("hello there");
14600
14601            println!("around the");
14602            println!("world");
14603        }
14604        "#
14605        .unindent(),
14606    );
14607}
14608
14609#[test]
14610fn test_split_words() {
14611    fn split(text: &str) -> Vec<&str> {
14612        split_words(text).collect()
14613    }
14614
14615    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14616    assert_eq!(split("hello_world"), &["hello_", "world"]);
14617    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14618    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14619    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14620    assert_eq!(split("helloworld"), &["helloworld"]);
14621
14622    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14623}
14624
14625#[gpui::test]
14626async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14627    init_test(cx, |_| {});
14628
14629    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14630    let mut assert = |before, after| {
14631        let _state_context = cx.set_state(before);
14632        cx.run_until_parked();
14633        cx.update_editor(|editor, window, cx| {
14634            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14635        });
14636        cx.run_until_parked();
14637        cx.assert_editor_state(after);
14638    };
14639
14640    // Outside bracket jumps to outside of matching bracket
14641    assert("console.logˇ(var);", "console.log(var)ˇ;");
14642    assert("console.log(var)ˇ;", "console.logˇ(var);");
14643
14644    // Inside bracket jumps to inside of matching bracket
14645    assert("console.log(ˇvar);", "console.log(varˇ);");
14646    assert("console.log(varˇ);", "console.log(ˇvar);");
14647
14648    // When outside a bracket and inside, favor jumping to the inside bracket
14649    assert(
14650        "console.log('foo', [1, 2, 3]ˇ);",
14651        "console.log(ˇ'foo', [1, 2, 3]);",
14652    );
14653    assert(
14654        "console.log(ˇ'foo', [1, 2, 3]);",
14655        "console.log('foo', [1, 2, 3]ˇ);",
14656    );
14657
14658    // Bias forward if two options are equally likely
14659    assert(
14660        "let result = curried_fun()ˇ();",
14661        "let result = curried_fun()()ˇ;",
14662    );
14663
14664    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14665    assert(
14666        indoc! {"
14667            function test() {
14668                console.log('test')ˇ
14669            }"},
14670        indoc! {"
14671            function test() {
14672                console.logˇ('test')
14673            }"},
14674    );
14675}
14676
14677#[gpui::test]
14678async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14679    init_test(cx, |_| {});
14680
14681    let fs = FakeFs::new(cx.executor());
14682    fs.insert_tree(
14683        path!("/a"),
14684        json!({
14685            "main.rs": "fn main() { let a = 5; }",
14686            "other.rs": "// Test file",
14687        }),
14688    )
14689    .await;
14690    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14691
14692    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14693    language_registry.add(Arc::new(Language::new(
14694        LanguageConfig {
14695            name: "Rust".into(),
14696            matcher: LanguageMatcher {
14697                path_suffixes: vec!["rs".to_string()],
14698                ..Default::default()
14699            },
14700            brackets: BracketPairConfig {
14701                pairs: vec![BracketPair {
14702                    start: "{".to_string(),
14703                    end: "}".to_string(),
14704                    close: true,
14705                    surround: true,
14706                    newline: true,
14707                }],
14708                disabled_scopes_by_bracket_ix: Vec::new(),
14709            },
14710            ..Default::default()
14711        },
14712        Some(tree_sitter_rust::LANGUAGE.into()),
14713    )));
14714    let mut fake_servers = language_registry.register_fake_lsp(
14715        "Rust",
14716        FakeLspAdapter {
14717            capabilities: lsp::ServerCapabilities {
14718                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14719                    first_trigger_character: "{".to_string(),
14720                    more_trigger_character: None,
14721                }),
14722                ..Default::default()
14723            },
14724            ..Default::default()
14725        },
14726    );
14727
14728    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14729
14730    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14731
14732    let worktree_id = workspace
14733        .update(cx, |workspace, _, cx| {
14734            workspace.project().update(cx, |project, cx| {
14735                project.worktrees(cx).next().unwrap().read(cx).id()
14736            })
14737        })
14738        .unwrap();
14739
14740    let buffer = project
14741        .update(cx, |project, cx| {
14742            project.open_local_buffer(path!("/a/main.rs"), cx)
14743        })
14744        .await
14745        .unwrap();
14746    let editor_handle = workspace
14747        .update(cx, |workspace, window, cx| {
14748            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14749        })
14750        .unwrap()
14751        .await
14752        .unwrap()
14753        .downcast::<Editor>()
14754        .unwrap();
14755
14756    cx.executor().start_waiting();
14757    let fake_server = fake_servers.next().await.unwrap();
14758
14759    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14760        |params, _| async move {
14761            assert_eq!(
14762                params.text_document_position.text_document.uri,
14763                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14764            );
14765            assert_eq!(
14766                params.text_document_position.position,
14767                lsp::Position::new(0, 21),
14768            );
14769
14770            Ok(Some(vec![lsp::TextEdit {
14771                new_text: "]".to_string(),
14772                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14773            }]))
14774        },
14775    );
14776
14777    editor_handle.update_in(cx, |editor, window, cx| {
14778        window.focus(&editor.focus_handle(cx));
14779        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14780            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14781        });
14782        editor.handle_input("{", window, cx);
14783    });
14784
14785    cx.executor().run_until_parked();
14786
14787    buffer.update(cx, |buffer, _| {
14788        assert_eq!(
14789            buffer.text(),
14790            "fn main() { let a = {5}; }",
14791            "No extra braces from on type formatting should appear in the buffer"
14792        )
14793    });
14794}
14795
14796#[gpui::test(iterations = 20, seeds(31))]
14797async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14798    init_test(cx, |_| {});
14799
14800    let mut cx = EditorLspTestContext::new_rust(
14801        lsp::ServerCapabilities {
14802            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14803                first_trigger_character: ".".to_string(),
14804                more_trigger_character: None,
14805            }),
14806            ..Default::default()
14807        },
14808        cx,
14809    )
14810    .await;
14811
14812    cx.update_buffer(|buffer, _| {
14813        // This causes autoindent to be async.
14814        buffer.set_sync_parse_timeout(Duration::ZERO)
14815    });
14816
14817    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14818    cx.simulate_keystroke("\n");
14819    cx.run_until_parked();
14820
14821    let buffer_cloned =
14822        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14823    let mut request =
14824        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14825            let buffer_cloned = buffer_cloned.clone();
14826            async move {
14827                buffer_cloned.update(&mut cx, |buffer, _| {
14828                    assert_eq!(
14829                        buffer.text(),
14830                        "fn c() {\n    d()\n        .\n}\n",
14831                        "OnTypeFormatting should triggered after autoindent applied"
14832                    )
14833                })?;
14834
14835                Ok(Some(vec![]))
14836            }
14837        });
14838
14839    cx.simulate_keystroke(".");
14840    cx.run_until_parked();
14841
14842    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14843    assert!(request.next().await.is_some());
14844    request.close();
14845    assert!(request.next().await.is_none());
14846}
14847
14848#[gpui::test]
14849async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14850    init_test(cx, |_| {});
14851
14852    let fs = FakeFs::new(cx.executor());
14853    fs.insert_tree(
14854        path!("/a"),
14855        json!({
14856            "main.rs": "fn main() { let a = 5; }",
14857            "other.rs": "// Test file",
14858        }),
14859    )
14860    .await;
14861
14862    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14863
14864    let server_restarts = Arc::new(AtomicUsize::new(0));
14865    let closure_restarts = Arc::clone(&server_restarts);
14866    let language_server_name = "test language server";
14867    let language_name: LanguageName = "Rust".into();
14868
14869    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14870    language_registry.add(Arc::new(Language::new(
14871        LanguageConfig {
14872            name: language_name.clone(),
14873            matcher: LanguageMatcher {
14874                path_suffixes: vec!["rs".to_string()],
14875                ..Default::default()
14876            },
14877            ..Default::default()
14878        },
14879        Some(tree_sitter_rust::LANGUAGE.into()),
14880    )));
14881    let mut fake_servers = language_registry.register_fake_lsp(
14882        "Rust",
14883        FakeLspAdapter {
14884            name: language_server_name,
14885            initialization_options: Some(json!({
14886                "testOptionValue": true
14887            })),
14888            initializer: Some(Box::new(move |fake_server| {
14889                let task_restarts = Arc::clone(&closure_restarts);
14890                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14891                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14892                    futures::future::ready(Ok(()))
14893                });
14894            })),
14895            ..Default::default()
14896        },
14897    );
14898
14899    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14900    let _buffer = project
14901        .update(cx, |project, cx| {
14902            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14903        })
14904        .await
14905        .unwrap();
14906    let _fake_server = fake_servers.next().await.unwrap();
14907    update_test_language_settings(cx, |language_settings| {
14908        language_settings.languages.insert(
14909            language_name.clone(),
14910            LanguageSettingsContent {
14911                tab_size: NonZeroU32::new(8),
14912                ..Default::default()
14913            },
14914        );
14915    });
14916    cx.executor().run_until_parked();
14917    assert_eq!(
14918        server_restarts.load(atomic::Ordering::Acquire),
14919        0,
14920        "Should not restart LSP server on an unrelated change"
14921    );
14922
14923    update_test_project_settings(cx, |project_settings| {
14924        project_settings.lsp.insert(
14925            "Some other server name".into(),
14926            LspSettings {
14927                binary: None,
14928                settings: None,
14929                initialization_options: Some(json!({
14930                    "some other init value": false
14931                })),
14932                enable_lsp_tasks: false,
14933            },
14934        );
14935    });
14936    cx.executor().run_until_parked();
14937    assert_eq!(
14938        server_restarts.load(atomic::Ordering::Acquire),
14939        0,
14940        "Should not restart LSP server on an unrelated LSP settings change"
14941    );
14942
14943    update_test_project_settings(cx, |project_settings| {
14944        project_settings.lsp.insert(
14945            language_server_name.into(),
14946            LspSettings {
14947                binary: None,
14948                settings: None,
14949                initialization_options: Some(json!({
14950                    "anotherInitValue": false
14951                })),
14952                enable_lsp_tasks: false,
14953            },
14954        );
14955    });
14956    cx.executor().run_until_parked();
14957    assert_eq!(
14958        server_restarts.load(atomic::Ordering::Acquire),
14959        1,
14960        "Should restart LSP server on a related LSP settings change"
14961    );
14962
14963    update_test_project_settings(cx, |project_settings| {
14964        project_settings.lsp.insert(
14965            language_server_name.into(),
14966            LspSettings {
14967                binary: None,
14968                settings: None,
14969                initialization_options: Some(json!({
14970                    "anotherInitValue": false
14971                })),
14972                enable_lsp_tasks: false,
14973            },
14974        );
14975    });
14976    cx.executor().run_until_parked();
14977    assert_eq!(
14978        server_restarts.load(atomic::Ordering::Acquire),
14979        1,
14980        "Should not restart LSP server on a related LSP settings change that is the same"
14981    );
14982
14983    update_test_project_settings(cx, |project_settings| {
14984        project_settings.lsp.insert(
14985            language_server_name.into(),
14986            LspSettings {
14987                binary: None,
14988                settings: None,
14989                initialization_options: None,
14990                enable_lsp_tasks: false,
14991            },
14992        );
14993    });
14994    cx.executor().run_until_parked();
14995    assert_eq!(
14996        server_restarts.load(atomic::Ordering::Acquire),
14997        2,
14998        "Should restart LSP server on another related LSP settings change"
14999    );
15000}
15001
15002#[gpui::test]
15003async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15004    init_test(cx, |_| {});
15005
15006    let mut cx = EditorLspTestContext::new_rust(
15007        lsp::ServerCapabilities {
15008            completion_provider: Some(lsp::CompletionOptions {
15009                trigger_characters: Some(vec![".".to_string()]),
15010                resolve_provider: Some(true),
15011                ..Default::default()
15012            }),
15013            ..Default::default()
15014        },
15015        cx,
15016    )
15017    .await;
15018
15019    cx.set_state("fn main() { let a = 2ˇ; }");
15020    cx.simulate_keystroke(".");
15021    let completion_item = lsp::CompletionItem {
15022        label: "some".into(),
15023        kind: Some(lsp::CompletionItemKind::SNIPPET),
15024        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15025        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15026            kind: lsp::MarkupKind::Markdown,
15027            value: "```rust\nSome(2)\n```".to_string(),
15028        })),
15029        deprecated: Some(false),
15030        sort_text: Some("fffffff2".to_string()),
15031        filter_text: Some("some".to_string()),
15032        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15033        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15034            range: lsp::Range {
15035                start: lsp::Position {
15036                    line: 0,
15037                    character: 22,
15038                },
15039                end: lsp::Position {
15040                    line: 0,
15041                    character: 22,
15042                },
15043            },
15044            new_text: "Some(2)".to_string(),
15045        })),
15046        additional_text_edits: Some(vec![lsp::TextEdit {
15047            range: lsp::Range {
15048                start: lsp::Position {
15049                    line: 0,
15050                    character: 20,
15051                },
15052                end: lsp::Position {
15053                    line: 0,
15054                    character: 22,
15055                },
15056            },
15057            new_text: "".to_string(),
15058        }]),
15059        ..Default::default()
15060    };
15061
15062    let closure_completion_item = completion_item.clone();
15063    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15064        let task_completion_item = closure_completion_item.clone();
15065        async move {
15066            Ok(Some(lsp::CompletionResponse::Array(vec![
15067                task_completion_item,
15068            ])))
15069        }
15070    });
15071
15072    request.next().await;
15073
15074    cx.condition(|editor, _| editor.context_menu_visible())
15075        .await;
15076    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15077        editor
15078            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15079            .unwrap()
15080    });
15081    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15082
15083    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15084        let task_completion_item = completion_item.clone();
15085        async move { Ok(task_completion_item) }
15086    })
15087    .next()
15088    .await
15089    .unwrap();
15090    apply_additional_edits.await.unwrap();
15091    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15092}
15093
15094#[gpui::test]
15095async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15096    init_test(cx, |_| {});
15097
15098    let mut cx = EditorLspTestContext::new_rust(
15099        lsp::ServerCapabilities {
15100            completion_provider: Some(lsp::CompletionOptions {
15101                trigger_characters: Some(vec![".".to_string()]),
15102                resolve_provider: Some(true),
15103                ..Default::default()
15104            }),
15105            ..Default::default()
15106        },
15107        cx,
15108    )
15109    .await;
15110
15111    cx.set_state("fn main() { let a = 2ˇ; }");
15112    cx.simulate_keystroke(".");
15113
15114    let item1 = lsp::CompletionItem {
15115        label: "method id()".to_string(),
15116        filter_text: Some("id".to_string()),
15117        detail: None,
15118        documentation: None,
15119        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15120            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15121            new_text: ".id".to_string(),
15122        })),
15123        ..lsp::CompletionItem::default()
15124    };
15125
15126    let item2 = lsp::CompletionItem {
15127        label: "other".to_string(),
15128        filter_text: Some("other".to_string()),
15129        detail: None,
15130        documentation: None,
15131        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15132            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15133            new_text: ".other".to_string(),
15134        })),
15135        ..lsp::CompletionItem::default()
15136    };
15137
15138    let item1 = item1.clone();
15139    cx.set_request_handler::<lsp::request::Completion, _, _>({
15140        let item1 = item1.clone();
15141        move |_, _, _| {
15142            let item1 = item1.clone();
15143            let item2 = item2.clone();
15144            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15145        }
15146    })
15147    .next()
15148    .await;
15149
15150    cx.condition(|editor, _| editor.context_menu_visible())
15151        .await;
15152    cx.update_editor(|editor, _, _| {
15153        let context_menu = editor.context_menu.borrow_mut();
15154        let context_menu = context_menu
15155            .as_ref()
15156            .expect("Should have the context menu deployed");
15157        match context_menu {
15158            CodeContextMenu::Completions(completions_menu) => {
15159                let completions = completions_menu.completions.borrow_mut();
15160                assert_eq!(
15161                    completions
15162                        .iter()
15163                        .map(|completion| &completion.label.text)
15164                        .collect::<Vec<_>>(),
15165                    vec!["method id()", "other"]
15166                )
15167            }
15168            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15169        }
15170    });
15171
15172    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15173        let item1 = item1.clone();
15174        move |_, item_to_resolve, _| {
15175            let item1 = item1.clone();
15176            async move {
15177                if item1 == item_to_resolve {
15178                    Ok(lsp::CompletionItem {
15179                        label: "method id()".to_string(),
15180                        filter_text: Some("id".to_string()),
15181                        detail: Some("Now resolved!".to_string()),
15182                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15183                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15184                            range: lsp::Range::new(
15185                                lsp::Position::new(0, 22),
15186                                lsp::Position::new(0, 22),
15187                            ),
15188                            new_text: ".id".to_string(),
15189                        })),
15190                        ..lsp::CompletionItem::default()
15191                    })
15192                } else {
15193                    Ok(item_to_resolve)
15194                }
15195            }
15196        }
15197    })
15198    .next()
15199    .await
15200    .unwrap();
15201    cx.run_until_parked();
15202
15203    cx.update_editor(|editor, window, cx| {
15204        editor.context_menu_next(&Default::default(), window, cx);
15205    });
15206
15207    cx.update_editor(|editor, _, _| {
15208        let context_menu = editor.context_menu.borrow_mut();
15209        let context_menu = context_menu
15210            .as_ref()
15211            .expect("Should have the context menu deployed");
15212        match context_menu {
15213            CodeContextMenu::Completions(completions_menu) => {
15214                let completions = completions_menu.completions.borrow_mut();
15215                assert_eq!(
15216                    completions
15217                        .iter()
15218                        .map(|completion| &completion.label.text)
15219                        .collect::<Vec<_>>(),
15220                    vec!["method id() Now resolved!", "other"],
15221                    "Should update first completion label, but not second as the filter text did not match."
15222                );
15223            }
15224            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15225        }
15226    });
15227}
15228
15229#[gpui::test]
15230async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15231    init_test(cx, |_| {});
15232    let mut cx = EditorLspTestContext::new_rust(
15233        lsp::ServerCapabilities {
15234            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15235            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15236            completion_provider: Some(lsp::CompletionOptions {
15237                resolve_provider: Some(true),
15238                ..Default::default()
15239            }),
15240            ..Default::default()
15241        },
15242        cx,
15243    )
15244    .await;
15245    cx.set_state(indoc! {"
15246        struct TestStruct {
15247            field: i32
15248        }
15249
15250        fn mainˇ() {
15251            let unused_var = 42;
15252            let test_struct = TestStruct { field: 42 };
15253        }
15254    "});
15255    let symbol_range = cx.lsp_range(indoc! {"
15256        struct TestStruct {
15257            field: i32
15258        }
15259
15260        «fn main»() {
15261            let unused_var = 42;
15262            let test_struct = TestStruct { field: 42 };
15263        }
15264    "});
15265    let mut hover_requests =
15266        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15267            Ok(Some(lsp::Hover {
15268                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15269                    kind: lsp::MarkupKind::Markdown,
15270                    value: "Function documentation".to_string(),
15271                }),
15272                range: Some(symbol_range),
15273            }))
15274        });
15275
15276    // Case 1: Test that code action menu hide hover popover
15277    cx.dispatch_action(Hover);
15278    hover_requests.next().await;
15279    cx.condition(|editor, _| editor.hover_state.visible()).await;
15280    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15281        move |_, _, _| async move {
15282            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15283                lsp::CodeAction {
15284                    title: "Remove unused variable".to_string(),
15285                    kind: Some(CodeActionKind::QUICKFIX),
15286                    edit: Some(lsp::WorkspaceEdit {
15287                        changes: Some(
15288                            [(
15289                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15290                                vec![lsp::TextEdit {
15291                                    range: lsp::Range::new(
15292                                        lsp::Position::new(5, 4),
15293                                        lsp::Position::new(5, 27),
15294                                    ),
15295                                    new_text: "".to_string(),
15296                                }],
15297                            )]
15298                            .into_iter()
15299                            .collect(),
15300                        ),
15301                        ..Default::default()
15302                    }),
15303                    ..Default::default()
15304                },
15305            )]))
15306        },
15307    );
15308    cx.update_editor(|editor, window, cx| {
15309        editor.toggle_code_actions(
15310            &ToggleCodeActions {
15311                deployed_from: None,
15312                quick_launch: false,
15313            },
15314            window,
15315            cx,
15316        );
15317    });
15318    code_action_requests.next().await;
15319    cx.run_until_parked();
15320    cx.condition(|editor, _| editor.context_menu_visible())
15321        .await;
15322    cx.update_editor(|editor, _, _| {
15323        assert!(
15324            !editor.hover_state.visible(),
15325            "Hover popover should be hidden when code action menu is shown"
15326        );
15327        // Hide code actions
15328        editor.context_menu.take();
15329    });
15330
15331    // Case 2: Test that code completions hide hover popover
15332    cx.dispatch_action(Hover);
15333    hover_requests.next().await;
15334    cx.condition(|editor, _| editor.hover_state.visible()).await;
15335    let counter = Arc::new(AtomicUsize::new(0));
15336    let mut completion_requests =
15337        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15338            let counter = counter.clone();
15339            async move {
15340                counter.fetch_add(1, atomic::Ordering::Release);
15341                Ok(Some(lsp::CompletionResponse::Array(vec![
15342                    lsp::CompletionItem {
15343                        label: "main".into(),
15344                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15345                        detail: Some("() -> ()".to_string()),
15346                        ..Default::default()
15347                    },
15348                    lsp::CompletionItem {
15349                        label: "TestStruct".into(),
15350                        kind: Some(lsp::CompletionItemKind::STRUCT),
15351                        detail: Some("struct TestStruct".to_string()),
15352                        ..Default::default()
15353                    },
15354                ])))
15355            }
15356        });
15357    cx.update_editor(|editor, window, cx| {
15358        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15359    });
15360    completion_requests.next().await;
15361    cx.condition(|editor, _| editor.context_menu_visible())
15362        .await;
15363    cx.update_editor(|editor, _, _| {
15364        assert!(
15365            !editor.hover_state.visible(),
15366            "Hover popover should be hidden when completion menu is shown"
15367        );
15368    });
15369}
15370
15371#[gpui::test]
15372async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15373    init_test(cx, |_| {});
15374
15375    let mut cx = EditorLspTestContext::new_rust(
15376        lsp::ServerCapabilities {
15377            completion_provider: Some(lsp::CompletionOptions {
15378                trigger_characters: Some(vec![".".to_string()]),
15379                resolve_provider: Some(true),
15380                ..Default::default()
15381            }),
15382            ..Default::default()
15383        },
15384        cx,
15385    )
15386    .await;
15387
15388    cx.set_state("fn main() { let a = 2ˇ; }");
15389    cx.simulate_keystroke(".");
15390
15391    let unresolved_item_1 = lsp::CompletionItem {
15392        label: "id".to_string(),
15393        filter_text: Some("id".to_string()),
15394        detail: None,
15395        documentation: None,
15396        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15397            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15398            new_text: ".id".to_string(),
15399        })),
15400        ..lsp::CompletionItem::default()
15401    };
15402    let resolved_item_1 = lsp::CompletionItem {
15403        additional_text_edits: Some(vec![lsp::TextEdit {
15404            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15405            new_text: "!!".to_string(),
15406        }]),
15407        ..unresolved_item_1.clone()
15408    };
15409    let unresolved_item_2 = lsp::CompletionItem {
15410        label: "other".to_string(),
15411        filter_text: Some("other".to_string()),
15412        detail: None,
15413        documentation: None,
15414        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15415            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15416            new_text: ".other".to_string(),
15417        })),
15418        ..lsp::CompletionItem::default()
15419    };
15420    let resolved_item_2 = lsp::CompletionItem {
15421        additional_text_edits: Some(vec![lsp::TextEdit {
15422            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15423            new_text: "??".to_string(),
15424        }]),
15425        ..unresolved_item_2.clone()
15426    };
15427
15428    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15429    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15430    cx.lsp
15431        .server
15432        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15433            let unresolved_item_1 = unresolved_item_1.clone();
15434            let resolved_item_1 = resolved_item_1.clone();
15435            let unresolved_item_2 = unresolved_item_2.clone();
15436            let resolved_item_2 = resolved_item_2.clone();
15437            let resolve_requests_1 = resolve_requests_1.clone();
15438            let resolve_requests_2 = resolve_requests_2.clone();
15439            move |unresolved_request, _| {
15440                let unresolved_item_1 = unresolved_item_1.clone();
15441                let resolved_item_1 = resolved_item_1.clone();
15442                let unresolved_item_2 = unresolved_item_2.clone();
15443                let resolved_item_2 = resolved_item_2.clone();
15444                let resolve_requests_1 = resolve_requests_1.clone();
15445                let resolve_requests_2 = resolve_requests_2.clone();
15446                async move {
15447                    if unresolved_request == unresolved_item_1 {
15448                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15449                        Ok(resolved_item_1.clone())
15450                    } else if unresolved_request == unresolved_item_2 {
15451                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15452                        Ok(resolved_item_2.clone())
15453                    } else {
15454                        panic!("Unexpected completion item {unresolved_request:?}")
15455                    }
15456                }
15457            }
15458        })
15459        .detach();
15460
15461    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15462        let unresolved_item_1 = unresolved_item_1.clone();
15463        let unresolved_item_2 = unresolved_item_2.clone();
15464        async move {
15465            Ok(Some(lsp::CompletionResponse::Array(vec![
15466                unresolved_item_1,
15467                unresolved_item_2,
15468            ])))
15469        }
15470    })
15471    .next()
15472    .await;
15473
15474    cx.condition(|editor, _| editor.context_menu_visible())
15475        .await;
15476    cx.update_editor(|editor, _, _| {
15477        let context_menu = editor.context_menu.borrow_mut();
15478        let context_menu = context_menu
15479            .as_ref()
15480            .expect("Should have the context menu deployed");
15481        match context_menu {
15482            CodeContextMenu::Completions(completions_menu) => {
15483                let completions = completions_menu.completions.borrow_mut();
15484                assert_eq!(
15485                    completions
15486                        .iter()
15487                        .map(|completion| &completion.label.text)
15488                        .collect::<Vec<_>>(),
15489                    vec!["id", "other"]
15490                )
15491            }
15492            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15493        }
15494    });
15495    cx.run_until_parked();
15496
15497    cx.update_editor(|editor, window, cx| {
15498        editor.context_menu_next(&ContextMenuNext, window, cx);
15499    });
15500    cx.run_until_parked();
15501    cx.update_editor(|editor, window, cx| {
15502        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15503    });
15504    cx.run_until_parked();
15505    cx.update_editor(|editor, window, cx| {
15506        editor.context_menu_next(&ContextMenuNext, window, cx);
15507    });
15508    cx.run_until_parked();
15509    cx.update_editor(|editor, window, cx| {
15510        editor
15511            .compose_completion(&ComposeCompletion::default(), window, cx)
15512            .expect("No task returned")
15513    })
15514    .await
15515    .expect("Completion failed");
15516    cx.run_until_parked();
15517
15518    cx.update_editor(|editor, _, cx| {
15519        assert_eq!(
15520            resolve_requests_1.load(atomic::Ordering::Acquire),
15521            1,
15522            "Should always resolve once despite multiple selections"
15523        );
15524        assert_eq!(
15525            resolve_requests_2.load(atomic::Ordering::Acquire),
15526            1,
15527            "Should always resolve once after multiple selections and applying the completion"
15528        );
15529        assert_eq!(
15530            editor.text(cx),
15531            "fn main() { let a = ??.other; }",
15532            "Should use resolved data when applying the completion"
15533        );
15534    });
15535}
15536
15537#[gpui::test]
15538async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15539    init_test(cx, |_| {});
15540
15541    let item_0 = lsp::CompletionItem {
15542        label: "abs".into(),
15543        insert_text: Some("abs".into()),
15544        data: Some(json!({ "very": "special"})),
15545        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15546        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15547            lsp::InsertReplaceEdit {
15548                new_text: "abs".to_string(),
15549                insert: lsp::Range::default(),
15550                replace: lsp::Range::default(),
15551            },
15552        )),
15553        ..lsp::CompletionItem::default()
15554    };
15555    let items = iter::once(item_0.clone())
15556        .chain((11..51).map(|i| lsp::CompletionItem {
15557            label: format!("item_{}", i),
15558            insert_text: Some(format!("item_{}", i)),
15559            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15560            ..lsp::CompletionItem::default()
15561        }))
15562        .collect::<Vec<_>>();
15563
15564    let default_commit_characters = vec!["?".to_string()];
15565    let default_data = json!({ "default": "data"});
15566    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15567    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15568    let default_edit_range = lsp::Range {
15569        start: lsp::Position {
15570            line: 0,
15571            character: 5,
15572        },
15573        end: lsp::Position {
15574            line: 0,
15575            character: 5,
15576        },
15577    };
15578
15579    let mut cx = EditorLspTestContext::new_rust(
15580        lsp::ServerCapabilities {
15581            completion_provider: Some(lsp::CompletionOptions {
15582                trigger_characters: Some(vec![".".to_string()]),
15583                resolve_provider: Some(true),
15584                ..Default::default()
15585            }),
15586            ..Default::default()
15587        },
15588        cx,
15589    )
15590    .await;
15591
15592    cx.set_state("fn main() { let a = 2ˇ; }");
15593    cx.simulate_keystroke(".");
15594
15595    let completion_data = default_data.clone();
15596    let completion_characters = default_commit_characters.clone();
15597    let completion_items = items.clone();
15598    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15599        let default_data = completion_data.clone();
15600        let default_commit_characters = completion_characters.clone();
15601        let items = completion_items.clone();
15602        async move {
15603            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15604                items,
15605                item_defaults: Some(lsp::CompletionListItemDefaults {
15606                    data: Some(default_data.clone()),
15607                    commit_characters: Some(default_commit_characters.clone()),
15608                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15609                        default_edit_range,
15610                    )),
15611                    insert_text_format: Some(default_insert_text_format),
15612                    insert_text_mode: Some(default_insert_text_mode),
15613                }),
15614                ..lsp::CompletionList::default()
15615            })))
15616        }
15617    })
15618    .next()
15619    .await;
15620
15621    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15622    cx.lsp
15623        .server
15624        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15625            let closure_resolved_items = resolved_items.clone();
15626            move |item_to_resolve, _| {
15627                let closure_resolved_items = closure_resolved_items.clone();
15628                async move {
15629                    closure_resolved_items.lock().push(item_to_resolve.clone());
15630                    Ok(item_to_resolve)
15631                }
15632            }
15633        })
15634        .detach();
15635
15636    cx.condition(|editor, _| editor.context_menu_visible())
15637        .await;
15638    cx.run_until_parked();
15639    cx.update_editor(|editor, _, _| {
15640        let menu = editor.context_menu.borrow_mut();
15641        match menu.as_ref().expect("should have the completions menu") {
15642            CodeContextMenu::Completions(completions_menu) => {
15643                assert_eq!(
15644                    completions_menu
15645                        .entries
15646                        .borrow()
15647                        .iter()
15648                        .map(|mat| mat.string.clone())
15649                        .collect::<Vec<String>>(),
15650                    items
15651                        .iter()
15652                        .map(|completion| completion.label.clone())
15653                        .collect::<Vec<String>>()
15654                );
15655            }
15656            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15657        }
15658    });
15659    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15660    // with 4 from the end.
15661    assert_eq!(
15662        *resolved_items.lock(),
15663        [&items[0..16], &items[items.len() - 4..items.len()]]
15664            .concat()
15665            .iter()
15666            .cloned()
15667            .map(|mut item| {
15668                if item.data.is_none() {
15669                    item.data = Some(default_data.clone());
15670                }
15671                item
15672            })
15673            .collect::<Vec<lsp::CompletionItem>>(),
15674        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15675    );
15676    resolved_items.lock().clear();
15677
15678    cx.update_editor(|editor, window, cx| {
15679        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15680    });
15681    cx.run_until_parked();
15682    // Completions that have already been resolved are skipped.
15683    assert_eq!(
15684        *resolved_items.lock(),
15685        items[items.len() - 17..items.len() - 4]
15686            .iter()
15687            .cloned()
15688            .map(|mut item| {
15689                if item.data.is_none() {
15690                    item.data = Some(default_data.clone());
15691                }
15692                item
15693            })
15694            .collect::<Vec<lsp::CompletionItem>>()
15695    );
15696    resolved_items.lock().clear();
15697}
15698
15699#[gpui::test]
15700async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15701    init_test(cx, |_| {});
15702
15703    let mut cx = EditorLspTestContext::new(
15704        Language::new(
15705            LanguageConfig {
15706                matcher: LanguageMatcher {
15707                    path_suffixes: vec!["jsx".into()],
15708                    ..Default::default()
15709                },
15710                overrides: [(
15711                    "element".into(),
15712                    LanguageConfigOverride {
15713                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15714                        ..Default::default()
15715                    },
15716                )]
15717                .into_iter()
15718                .collect(),
15719                ..Default::default()
15720            },
15721            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15722        )
15723        .with_override_query("(jsx_self_closing_element) @element")
15724        .unwrap(),
15725        lsp::ServerCapabilities {
15726            completion_provider: Some(lsp::CompletionOptions {
15727                trigger_characters: Some(vec![":".to_string()]),
15728                ..Default::default()
15729            }),
15730            ..Default::default()
15731        },
15732        cx,
15733    )
15734    .await;
15735
15736    cx.lsp
15737        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15738            Ok(Some(lsp::CompletionResponse::Array(vec![
15739                lsp::CompletionItem {
15740                    label: "bg-blue".into(),
15741                    ..Default::default()
15742                },
15743                lsp::CompletionItem {
15744                    label: "bg-red".into(),
15745                    ..Default::default()
15746                },
15747                lsp::CompletionItem {
15748                    label: "bg-yellow".into(),
15749                    ..Default::default()
15750                },
15751            ])))
15752        });
15753
15754    cx.set_state(r#"<p class="bgˇ" />"#);
15755
15756    // Trigger completion when typing a dash, because the dash is an extra
15757    // word character in the 'element' scope, which contains the cursor.
15758    cx.simulate_keystroke("-");
15759    cx.executor().run_until_parked();
15760    cx.update_editor(|editor, _, _| {
15761        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15762        {
15763            assert_eq!(
15764                completion_menu_entries(&menu),
15765                &["bg-blue", "bg-red", "bg-yellow"]
15766            );
15767        } else {
15768            panic!("expected completion menu to be open");
15769        }
15770    });
15771
15772    cx.simulate_keystroke("l");
15773    cx.executor().run_until_parked();
15774    cx.update_editor(|editor, _, _| {
15775        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15776        {
15777            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15778        } else {
15779            panic!("expected completion menu to be open");
15780        }
15781    });
15782
15783    // When filtering completions, consider the character after the '-' to
15784    // be the start of a subword.
15785    cx.set_state(r#"<p class="yelˇ" />"#);
15786    cx.simulate_keystroke("l");
15787    cx.executor().run_until_parked();
15788    cx.update_editor(|editor, _, _| {
15789        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15790        {
15791            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15792        } else {
15793            panic!("expected completion menu to be open");
15794        }
15795    });
15796}
15797
15798fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15799    let entries = menu.entries.borrow();
15800    entries.iter().map(|mat| mat.string.clone()).collect()
15801}
15802
15803#[gpui::test]
15804async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15805    init_test(cx, |settings| {
15806        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15807            FormatterList(vec![Formatter::Prettier].into()),
15808        ))
15809    });
15810
15811    let fs = FakeFs::new(cx.executor());
15812    fs.insert_file(path!("/file.ts"), Default::default()).await;
15813
15814    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15815    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15816
15817    language_registry.add(Arc::new(Language::new(
15818        LanguageConfig {
15819            name: "TypeScript".into(),
15820            matcher: LanguageMatcher {
15821                path_suffixes: vec!["ts".to_string()],
15822                ..Default::default()
15823            },
15824            ..Default::default()
15825        },
15826        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15827    )));
15828    update_test_language_settings(cx, |settings| {
15829        settings.defaults.prettier = Some(PrettierSettings {
15830            allowed: true,
15831            ..PrettierSettings::default()
15832        });
15833    });
15834
15835    let test_plugin = "test_plugin";
15836    let _ = language_registry.register_fake_lsp(
15837        "TypeScript",
15838        FakeLspAdapter {
15839            prettier_plugins: vec![test_plugin],
15840            ..Default::default()
15841        },
15842    );
15843
15844    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15845    let buffer = project
15846        .update(cx, |project, cx| {
15847            project.open_local_buffer(path!("/file.ts"), cx)
15848        })
15849        .await
15850        .unwrap();
15851
15852    let buffer_text = "one\ntwo\nthree\n";
15853    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15854    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15855    editor.update_in(cx, |editor, window, cx| {
15856        editor.set_text(buffer_text, window, cx)
15857    });
15858
15859    editor
15860        .update_in(cx, |editor, window, cx| {
15861            editor.perform_format(
15862                project.clone(),
15863                FormatTrigger::Manual,
15864                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15865                window,
15866                cx,
15867            )
15868        })
15869        .unwrap()
15870        .await;
15871    assert_eq!(
15872        editor.update(cx, |editor, cx| editor.text(cx)),
15873        buffer_text.to_string() + prettier_format_suffix,
15874        "Test prettier formatting was not applied to the original buffer text",
15875    );
15876
15877    update_test_language_settings(cx, |settings| {
15878        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15879    });
15880    let format = editor.update_in(cx, |editor, window, cx| {
15881        editor.perform_format(
15882            project.clone(),
15883            FormatTrigger::Manual,
15884            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15885            window,
15886            cx,
15887        )
15888    });
15889    format.await.unwrap();
15890    assert_eq!(
15891        editor.update(cx, |editor, cx| editor.text(cx)),
15892        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15893        "Autoformatting (via test prettier) was not applied to the original buffer text",
15894    );
15895}
15896
15897#[gpui::test]
15898async fn test_addition_reverts(cx: &mut TestAppContext) {
15899    init_test(cx, |_| {});
15900    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15901    let base_text = indoc! {r#"
15902        struct Row;
15903        struct Row1;
15904        struct Row2;
15905
15906        struct Row4;
15907        struct Row5;
15908        struct Row6;
15909
15910        struct Row8;
15911        struct Row9;
15912        struct Row10;"#};
15913
15914    // When addition hunks are not adjacent to carets, no hunk revert is performed
15915    assert_hunk_revert(
15916        indoc! {r#"struct Row;
15917                   struct Row1;
15918                   struct Row1.1;
15919                   struct Row1.2;
15920                   struct Row2;ˇ
15921
15922                   struct Row4;
15923                   struct Row5;
15924                   struct Row6;
15925
15926                   struct Row8;
15927                   ˇstruct Row9;
15928                   struct Row9.1;
15929                   struct Row9.2;
15930                   struct Row9.3;
15931                   struct Row10;"#},
15932        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15933        indoc! {r#"struct Row;
15934                   struct Row1;
15935                   struct Row1.1;
15936                   struct Row1.2;
15937                   struct Row2;ˇ
15938
15939                   struct Row4;
15940                   struct Row5;
15941                   struct Row6;
15942
15943                   struct Row8;
15944                   ˇstruct Row9;
15945                   struct Row9.1;
15946                   struct Row9.2;
15947                   struct Row9.3;
15948                   struct Row10;"#},
15949        base_text,
15950        &mut cx,
15951    );
15952    // Same for selections
15953    assert_hunk_revert(
15954        indoc! {r#"struct Row;
15955                   struct Row1;
15956                   struct Row2;
15957                   struct Row2.1;
15958                   struct Row2.2;
15959                   «ˇ
15960                   struct Row4;
15961                   struct» Row5;
15962                   «struct Row6;
15963                   ˇ»
15964                   struct Row9.1;
15965                   struct Row9.2;
15966                   struct Row9.3;
15967                   struct Row8;
15968                   struct Row9;
15969                   struct Row10;"#},
15970        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15971        indoc! {r#"struct Row;
15972                   struct Row1;
15973                   struct Row2;
15974                   struct Row2.1;
15975                   struct Row2.2;
15976                   «ˇ
15977                   struct Row4;
15978                   struct» Row5;
15979                   «struct Row6;
15980                   ˇ»
15981                   struct Row9.1;
15982                   struct Row9.2;
15983                   struct Row9.3;
15984                   struct Row8;
15985                   struct Row9;
15986                   struct Row10;"#},
15987        base_text,
15988        &mut cx,
15989    );
15990
15991    // When carets and selections intersect the addition hunks, those are reverted.
15992    // Adjacent carets got merged.
15993    assert_hunk_revert(
15994        indoc! {r#"struct Row;
15995                   ˇ// something on the top
15996                   struct Row1;
15997                   struct Row2;
15998                   struct Roˇw3.1;
15999                   struct Row2.2;
16000                   struct Row2.3;ˇ
16001
16002                   struct Row4;
16003                   struct ˇRow5.1;
16004                   struct Row5.2;
16005                   struct «Rowˇ»5.3;
16006                   struct Row5;
16007                   struct Row6;
16008                   ˇ
16009                   struct Row9.1;
16010                   struct «Rowˇ»9.2;
16011                   struct «ˇRow»9.3;
16012                   struct Row8;
16013                   struct Row9;
16014                   «ˇ// something on bottom»
16015                   struct Row10;"#},
16016        vec![
16017            DiffHunkStatusKind::Added,
16018            DiffHunkStatusKind::Added,
16019            DiffHunkStatusKind::Added,
16020            DiffHunkStatusKind::Added,
16021            DiffHunkStatusKind::Added,
16022        ],
16023        indoc! {r#"struct Row;
16024                   ˇstruct Row1;
16025                   struct Row2;
16026                   ˇ
16027                   struct Row4;
16028                   ˇstruct Row5;
16029                   struct Row6;
16030                   ˇ
16031                   ˇstruct Row8;
16032                   struct Row9;
16033                   ˇstruct Row10;"#},
16034        base_text,
16035        &mut cx,
16036    );
16037}
16038
16039#[gpui::test]
16040async fn test_modification_reverts(cx: &mut TestAppContext) {
16041    init_test(cx, |_| {});
16042    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16043    let base_text = indoc! {r#"
16044        struct Row;
16045        struct Row1;
16046        struct Row2;
16047
16048        struct Row4;
16049        struct Row5;
16050        struct Row6;
16051
16052        struct Row8;
16053        struct Row9;
16054        struct Row10;"#};
16055
16056    // Modification hunks behave the same as the addition ones.
16057    assert_hunk_revert(
16058        indoc! {r#"struct Row;
16059                   struct Row1;
16060                   struct Row33;
16061                   ˇ
16062                   struct Row4;
16063                   struct Row5;
16064                   struct Row6;
16065                   ˇ
16066                   struct Row99;
16067                   struct Row9;
16068                   struct Row10;"#},
16069        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16070        indoc! {r#"struct Row;
16071                   struct Row1;
16072                   struct Row33;
16073                   ˇ
16074                   struct Row4;
16075                   struct Row5;
16076                   struct Row6;
16077                   ˇ
16078                   struct Row99;
16079                   struct Row9;
16080                   struct Row10;"#},
16081        base_text,
16082        &mut cx,
16083    );
16084    assert_hunk_revert(
16085        indoc! {r#"struct Row;
16086                   struct Row1;
16087                   struct Row33;
16088                   «ˇ
16089                   struct Row4;
16090                   struct» Row5;
16091                   «struct Row6;
16092                   ˇ»
16093                   struct Row99;
16094                   struct Row9;
16095                   struct Row10;"#},
16096        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16097        indoc! {r#"struct Row;
16098                   struct Row1;
16099                   struct Row33;
16100                   «ˇ
16101                   struct Row4;
16102                   struct» Row5;
16103                   «struct Row6;
16104                   ˇ»
16105                   struct Row99;
16106                   struct Row9;
16107                   struct Row10;"#},
16108        base_text,
16109        &mut cx,
16110    );
16111
16112    assert_hunk_revert(
16113        indoc! {r#"ˇstruct Row1.1;
16114                   struct Row1;
16115                   «ˇstr»uct Row22;
16116
16117                   struct ˇRow44;
16118                   struct Row5;
16119                   struct «Rˇ»ow66;ˇ
16120
16121                   «struˇ»ct Row88;
16122                   struct Row9;
16123                   struct Row1011;ˇ"#},
16124        vec![
16125            DiffHunkStatusKind::Modified,
16126            DiffHunkStatusKind::Modified,
16127            DiffHunkStatusKind::Modified,
16128            DiffHunkStatusKind::Modified,
16129            DiffHunkStatusKind::Modified,
16130            DiffHunkStatusKind::Modified,
16131        ],
16132        indoc! {r#"struct Row;
16133                   ˇstruct Row1;
16134                   struct Row2;
16135                   ˇ
16136                   struct Row4;
16137                   ˇstruct Row5;
16138                   struct Row6;
16139                   ˇ
16140                   struct Row8;
16141                   ˇstruct Row9;
16142                   struct Row10;ˇ"#},
16143        base_text,
16144        &mut cx,
16145    );
16146}
16147
16148#[gpui::test]
16149async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16150    init_test(cx, |_| {});
16151    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16152    let base_text = indoc! {r#"
16153        one
16154
16155        two
16156        three
16157        "#};
16158
16159    cx.set_head_text(base_text);
16160    cx.set_state("\nˇ\n");
16161    cx.executor().run_until_parked();
16162    cx.update_editor(|editor, _window, cx| {
16163        editor.expand_selected_diff_hunks(cx);
16164    });
16165    cx.executor().run_until_parked();
16166    cx.update_editor(|editor, window, cx| {
16167        editor.backspace(&Default::default(), window, cx);
16168    });
16169    cx.run_until_parked();
16170    cx.assert_state_with_diff(
16171        indoc! {r#"
16172
16173        - two
16174        - threeˇ
16175        +
16176        "#}
16177        .to_string(),
16178    );
16179}
16180
16181#[gpui::test]
16182async fn test_deletion_reverts(cx: &mut TestAppContext) {
16183    init_test(cx, |_| {});
16184    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16185    let base_text = indoc! {r#"struct Row;
16186struct Row1;
16187struct Row2;
16188
16189struct Row4;
16190struct Row5;
16191struct Row6;
16192
16193struct Row8;
16194struct Row9;
16195struct Row10;"#};
16196
16197    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16198    assert_hunk_revert(
16199        indoc! {r#"struct Row;
16200                   struct Row2;
16201
16202                   ˇstruct Row4;
16203                   struct Row5;
16204                   struct Row6;
16205                   ˇ
16206                   struct Row8;
16207                   struct Row10;"#},
16208        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16209        indoc! {r#"struct Row;
16210                   struct Row2;
16211
16212                   ˇstruct Row4;
16213                   struct Row5;
16214                   struct Row6;
16215                   ˇ
16216                   struct Row8;
16217                   struct Row10;"#},
16218        base_text,
16219        &mut cx,
16220    );
16221    assert_hunk_revert(
16222        indoc! {r#"struct Row;
16223                   struct Row2;
16224
16225                   «ˇstruct Row4;
16226                   struct» Row5;
16227                   «struct Row6;
16228                   ˇ»
16229                   struct Row8;
16230                   struct Row10;"#},
16231        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16232        indoc! {r#"struct Row;
16233                   struct Row2;
16234
16235                   «ˇstruct Row4;
16236                   struct» Row5;
16237                   «struct Row6;
16238                   ˇ»
16239                   struct Row8;
16240                   struct Row10;"#},
16241        base_text,
16242        &mut cx,
16243    );
16244
16245    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16246    assert_hunk_revert(
16247        indoc! {r#"struct Row;
16248                   ˇstruct Row2;
16249
16250                   struct Row4;
16251                   struct Row5;
16252                   struct Row6;
16253
16254                   struct Row8;ˇ
16255                   struct Row10;"#},
16256        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16257        indoc! {r#"struct Row;
16258                   struct Row1;
16259                   ˇstruct Row2;
16260
16261                   struct Row4;
16262                   struct Row5;
16263                   struct Row6;
16264
16265                   struct Row8;ˇ
16266                   struct Row9;
16267                   struct Row10;"#},
16268        base_text,
16269        &mut cx,
16270    );
16271    assert_hunk_revert(
16272        indoc! {r#"struct Row;
16273                   struct Row2«ˇ;
16274                   struct Row4;
16275                   struct» Row5;
16276                   «struct Row6;
16277
16278                   struct Row8;ˇ»
16279                   struct Row10;"#},
16280        vec![
16281            DiffHunkStatusKind::Deleted,
16282            DiffHunkStatusKind::Deleted,
16283            DiffHunkStatusKind::Deleted,
16284        ],
16285        indoc! {r#"struct Row;
16286                   struct Row1;
16287                   struct Row2«ˇ;
16288
16289                   struct Row4;
16290                   struct» Row5;
16291                   «struct Row6;
16292
16293                   struct Row8;ˇ»
16294                   struct Row9;
16295                   struct Row10;"#},
16296        base_text,
16297        &mut cx,
16298    );
16299}
16300
16301#[gpui::test]
16302async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16303    init_test(cx, |_| {});
16304
16305    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16306    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16307    let base_text_3 =
16308        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16309
16310    let text_1 = edit_first_char_of_every_line(base_text_1);
16311    let text_2 = edit_first_char_of_every_line(base_text_2);
16312    let text_3 = edit_first_char_of_every_line(base_text_3);
16313
16314    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16315    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16316    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16317
16318    let multibuffer = cx.new(|cx| {
16319        let mut multibuffer = MultiBuffer::new(ReadWrite);
16320        multibuffer.push_excerpts(
16321            buffer_1.clone(),
16322            [
16323                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16324                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16325                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16326            ],
16327            cx,
16328        );
16329        multibuffer.push_excerpts(
16330            buffer_2.clone(),
16331            [
16332                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16333                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16334                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16335            ],
16336            cx,
16337        );
16338        multibuffer.push_excerpts(
16339            buffer_3.clone(),
16340            [
16341                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16342                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16343                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16344            ],
16345            cx,
16346        );
16347        multibuffer
16348    });
16349
16350    let fs = FakeFs::new(cx.executor());
16351    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16352    let (editor, cx) = cx
16353        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16354    editor.update_in(cx, |editor, _window, cx| {
16355        for (buffer, diff_base) in [
16356            (buffer_1.clone(), base_text_1),
16357            (buffer_2.clone(), base_text_2),
16358            (buffer_3.clone(), base_text_3),
16359        ] {
16360            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16361            editor
16362                .buffer
16363                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16364        }
16365    });
16366    cx.executor().run_until_parked();
16367
16368    editor.update_in(cx, |editor, window, cx| {
16369        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}");
16370        editor.select_all(&SelectAll, window, cx);
16371        editor.git_restore(&Default::default(), window, cx);
16372    });
16373    cx.executor().run_until_parked();
16374
16375    // When all ranges are selected, all buffer hunks are reverted.
16376    editor.update(cx, |editor, cx| {
16377        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");
16378    });
16379    buffer_1.update(cx, |buffer, _| {
16380        assert_eq!(buffer.text(), base_text_1);
16381    });
16382    buffer_2.update(cx, |buffer, _| {
16383        assert_eq!(buffer.text(), base_text_2);
16384    });
16385    buffer_3.update(cx, |buffer, _| {
16386        assert_eq!(buffer.text(), base_text_3);
16387    });
16388
16389    editor.update_in(cx, |editor, window, cx| {
16390        editor.undo(&Default::default(), window, cx);
16391    });
16392
16393    editor.update_in(cx, |editor, window, cx| {
16394        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16395            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16396        });
16397        editor.git_restore(&Default::default(), window, cx);
16398    });
16399
16400    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16401    // but not affect buffer_2 and its related excerpts.
16402    editor.update(cx, |editor, cx| {
16403        assert_eq!(
16404            editor.text(cx),
16405            "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}"
16406        );
16407    });
16408    buffer_1.update(cx, |buffer, _| {
16409        assert_eq!(buffer.text(), base_text_1);
16410    });
16411    buffer_2.update(cx, |buffer, _| {
16412        assert_eq!(
16413            buffer.text(),
16414            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16415        );
16416    });
16417    buffer_3.update(cx, |buffer, _| {
16418        assert_eq!(
16419            buffer.text(),
16420            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16421        );
16422    });
16423
16424    fn edit_first_char_of_every_line(text: &str) -> String {
16425        text.split('\n')
16426            .map(|line| format!("X{}", &line[1..]))
16427            .collect::<Vec<_>>()
16428            .join("\n")
16429    }
16430}
16431
16432#[gpui::test]
16433async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16434    init_test(cx, |_| {});
16435
16436    let cols = 4;
16437    let rows = 10;
16438    let sample_text_1 = sample_text(rows, cols, 'a');
16439    assert_eq!(
16440        sample_text_1,
16441        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16442    );
16443    let sample_text_2 = sample_text(rows, cols, 'l');
16444    assert_eq!(
16445        sample_text_2,
16446        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16447    );
16448    let sample_text_3 = sample_text(rows, cols, 'v');
16449    assert_eq!(
16450        sample_text_3,
16451        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16452    );
16453
16454    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16455    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16456    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16457
16458    let multi_buffer = cx.new(|cx| {
16459        let mut multibuffer = MultiBuffer::new(ReadWrite);
16460        multibuffer.push_excerpts(
16461            buffer_1.clone(),
16462            [
16463                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16464                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16465                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16466            ],
16467            cx,
16468        );
16469        multibuffer.push_excerpts(
16470            buffer_2.clone(),
16471            [
16472                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16473                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16474                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16475            ],
16476            cx,
16477        );
16478        multibuffer.push_excerpts(
16479            buffer_3.clone(),
16480            [
16481                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16482                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16483                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16484            ],
16485            cx,
16486        );
16487        multibuffer
16488    });
16489
16490    let fs = FakeFs::new(cx.executor());
16491    fs.insert_tree(
16492        "/a",
16493        json!({
16494            "main.rs": sample_text_1,
16495            "other.rs": sample_text_2,
16496            "lib.rs": sample_text_3,
16497        }),
16498    )
16499    .await;
16500    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16501    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16502    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16503    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16504        Editor::new(
16505            EditorMode::full(),
16506            multi_buffer,
16507            Some(project.clone()),
16508            window,
16509            cx,
16510        )
16511    });
16512    let multibuffer_item_id = workspace
16513        .update(cx, |workspace, window, cx| {
16514            assert!(
16515                workspace.active_item(cx).is_none(),
16516                "active item should be None before the first item is added"
16517            );
16518            workspace.add_item_to_active_pane(
16519                Box::new(multi_buffer_editor.clone()),
16520                None,
16521                true,
16522                window,
16523                cx,
16524            );
16525            let active_item = workspace
16526                .active_item(cx)
16527                .expect("should have an active item after adding the multi buffer");
16528            assert!(
16529                !active_item.is_singleton(cx),
16530                "A multi buffer was expected to active after adding"
16531            );
16532            active_item.item_id()
16533        })
16534        .unwrap();
16535    cx.executor().run_until_parked();
16536
16537    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16538        editor.change_selections(
16539            SelectionEffects::scroll(Autoscroll::Next),
16540            window,
16541            cx,
16542            |s| s.select_ranges(Some(1..2)),
16543        );
16544        editor.open_excerpts(&OpenExcerpts, window, cx);
16545    });
16546    cx.executor().run_until_parked();
16547    let first_item_id = workspace
16548        .update(cx, |workspace, window, cx| {
16549            let active_item = workspace
16550                .active_item(cx)
16551                .expect("should have an active item after navigating into the 1st buffer");
16552            let first_item_id = active_item.item_id();
16553            assert_ne!(
16554                first_item_id, multibuffer_item_id,
16555                "Should navigate into the 1st buffer and activate it"
16556            );
16557            assert!(
16558                active_item.is_singleton(cx),
16559                "New active item should be a singleton buffer"
16560            );
16561            assert_eq!(
16562                active_item
16563                    .act_as::<Editor>(cx)
16564                    .expect("should have navigated into an editor for the 1st buffer")
16565                    .read(cx)
16566                    .text(cx),
16567                sample_text_1
16568            );
16569
16570            workspace
16571                .go_back(workspace.active_pane().downgrade(), window, cx)
16572                .detach_and_log_err(cx);
16573
16574            first_item_id
16575        })
16576        .unwrap();
16577    cx.executor().run_until_parked();
16578    workspace
16579        .update(cx, |workspace, _, cx| {
16580            let active_item = workspace
16581                .active_item(cx)
16582                .expect("should have an active item after navigating back");
16583            assert_eq!(
16584                active_item.item_id(),
16585                multibuffer_item_id,
16586                "Should navigate back to the multi buffer"
16587            );
16588            assert!(!active_item.is_singleton(cx));
16589        })
16590        .unwrap();
16591
16592    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16593        editor.change_selections(
16594            SelectionEffects::scroll(Autoscroll::Next),
16595            window,
16596            cx,
16597            |s| s.select_ranges(Some(39..40)),
16598        );
16599        editor.open_excerpts(&OpenExcerpts, window, cx);
16600    });
16601    cx.executor().run_until_parked();
16602    let second_item_id = workspace
16603        .update(cx, |workspace, window, cx| {
16604            let active_item = workspace
16605                .active_item(cx)
16606                .expect("should have an active item after navigating into the 2nd buffer");
16607            let second_item_id = active_item.item_id();
16608            assert_ne!(
16609                second_item_id, multibuffer_item_id,
16610                "Should navigate away from the multibuffer"
16611            );
16612            assert_ne!(
16613                second_item_id, first_item_id,
16614                "Should navigate into the 2nd buffer and activate it"
16615            );
16616            assert!(
16617                active_item.is_singleton(cx),
16618                "New active item should be a singleton buffer"
16619            );
16620            assert_eq!(
16621                active_item
16622                    .act_as::<Editor>(cx)
16623                    .expect("should have navigated into an editor")
16624                    .read(cx)
16625                    .text(cx),
16626                sample_text_2
16627            );
16628
16629            workspace
16630                .go_back(workspace.active_pane().downgrade(), window, cx)
16631                .detach_and_log_err(cx);
16632
16633            second_item_id
16634        })
16635        .unwrap();
16636    cx.executor().run_until_parked();
16637    workspace
16638        .update(cx, |workspace, _, cx| {
16639            let active_item = workspace
16640                .active_item(cx)
16641                .expect("should have an active item after navigating back from the 2nd buffer");
16642            assert_eq!(
16643                active_item.item_id(),
16644                multibuffer_item_id,
16645                "Should navigate back from the 2nd buffer to the multi buffer"
16646            );
16647            assert!(!active_item.is_singleton(cx));
16648        })
16649        .unwrap();
16650
16651    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16652        editor.change_selections(
16653            SelectionEffects::scroll(Autoscroll::Next),
16654            window,
16655            cx,
16656            |s| s.select_ranges(Some(70..70)),
16657        );
16658        editor.open_excerpts(&OpenExcerpts, window, cx);
16659    });
16660    cx.executor().run_until_parked();
16661    workspace
16662        .update(cx, |workspace, window, cx| {
16663            let active_item = workspace
16664                .active_item(cx)
16665                .expect("should have an active item after navigating into the 3rd buffer");
16666            let third_item_id = active_item.item_id();
16667            assert_ne!(
16668                third_item_id, multibuffer_item_id,
16669                "Should navigate into the 3rd buffer and activate it"
16670            );
16671            assert_ne!(third_item_id, first_item_id);
16672            assert_ne!(third_item_id, second_item_id);
16673            assert!(
16674                active_item.is_singleton(cx),
16675                "New active item should be a singleton buffer"
16676            );
16677            assert_eq!(
16678                active_item
16679                    .act_as::<Editor>(cx)
16680                    .expect("should have navigated into an editor")
16681                    .read(cx)
16682                    .text(cx),
16683                sample_text_3
16684            );
16685
16686            workspace
16687                .go_back(workspace.active_pane().downgrade(), window, cx)
16688                .detach_and_log_err(cx);
16689        })
16690        .unwrap();
16691    cx.executor().run_until_parked();
16692    workspace
16693        .update(cx, |workspace, _, cx| {
16694            let active_item = workspace
16695                .active_item(cx)
16696                .expect("should have an active item after navigating back from the 3rd buffer");
16697            assert_eq!(
16698                active_item.item_id(),
16699                multibuffer_item_id,
16700                "Should navigate back from the 3rd buffer to the multi buffer"
16701            );
16702            assert!(!active_item.is_singleton(cx));
16703        })
16704        .unwrap();
16705}
16706
16707#[gpui::test]
16708async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16709    init_test(cx, |_| {});
16710
16711    let mut cx = EditorTestContext::new(cx).await;
16712
16713    let diff_base = r#"
16714        use some::mod;
16715
16716        const A: u32 = 42;
16717
16718        fn main() {
16719            println!("hello");
16720
16721            println!("world");
16722        }
16723        "#
16724    .unindent();
16725
16726    cx.set_state(
16727        &r#"
16728        use some::modified;
16729
16730        ˇ
16731        fn main() {
16732            println!("hello there");
16733
16734            println!("around the");
16735            println!("world");
16736        }
16737        "#
16738        .unindent(),
16739    );
16740
16741    cx.set_head_text(&diff_base);
16742    executor.run_until_parked();
16743
16744    cx.update_editor(|editor, window, cx| {
16745        editor.go_to_next_hunk(&GoToHunk, window, cx);
16746        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16747    });
16748    executor.run_until_parked();
16749    cx.assert_state_with_diff(
16750        r#"
16751          use some::modified;
16752
16753
16754          fn main() {
16755        -     println!("hello");
16756        + ˇ    println!("hello there");
16757
16758              println!("around the");
16759              println!("world");
16760          }
16761        "#
16762        .unindent(),
16763    );
16764
16765    cx.update_editor(|editor, window, cx| {
16766        for _ in 0..2 {
16767            editor.go_to_next_hunk(&GoToHunk, window, cx);
16768            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16769        }
16770    });
16771    executor.run_until_parked();
16772    cx.assert_state_with_diff(
16773        r#"
16774        - use some::mod;
16775        + ˇuse some::modified;
16776
16777
16778          fn main() {
16779        -     println!("hello");
16780        +     println!("hello there");
16781
16782        +     println!("around the");
16783              println!("world");
16784          }
16785        "#
16786        .unindent(),
16787    );
16788
16789    cx.update_editor(|editor, window, cx| {
16790        editor.go_to_next_hunk(&GoToHunk, window, cx);
16791        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16792    });
16793    executor.run_until_parked();
16794    cx.assert_state_with_diff(
16795        r#"
16796        - use some::mod;
16797        + use some::modified;
16798
16799        - const A: u32 = 42;
16800          ˇ
16801          fn main() {
16802        -     println!("hello");
16803        +     println!("hello there");
16804
16805        +     println!("around the");
16806              println!("world");
16807          }
16808        "#
16809        .unindent(),
16810    );
16811
16812    cx.update_editor(|editor, window, cx| {
16813        editor.cancel(&Cancel, window, cx);
16814    });
16815
16816    cx.assert_state_with_diff(
16817        r#"
16818          use some::modified;
16819
16820          ˇ
16821          fn main() {
16822              println!("hello there");
16823
16824              println!("around the");
16825              println!("world");
16826          }
16827        "#
16828        .unindent(),
16829    );
16830}
16831
16832#[gpui::test]
16833async fn test_diff_base_change_with_expanded_diff_hunks(
16834    executor: BackgroundExecutor,
16835    cx: &mut TestAppContext,
16836) {
16837    init_test(cx, |_| {});
16838
16839    let mut cx = EditorTestContext::new(cx).await;
16840
16841    let diff_base = r#"
16842        use some::mod1;
16843        use some::mod2;
16844
16845        const A: u32 = 42;
16846        const B: u32 = 42;
16847        const C: u32 = 42;
16848
16849        fn main() {
16850            println!("hello");
16851
16852            println!("world");
16853        }
16854        "#
16855    .unindent();
16856
16857    cx.set_state(
16858        &r#"
16859        use some::mod2;
16860
16861        const A: u32 = 42;
16862        const C: u32 = 42;
16863
16864        fn main(ˇ) {
16865            //println!("hello");
16866
16867            println!("world");
16868            //
16869            //
16870        }
16871        "#
16872        .unindent(),
16873    );
16874
16875    cx.set_head_text(&diff_base);
16876    executor.run_until_parked();
16877
16878    cx.update_editor(|editor, window, cx| {
16879        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16880    });
16881    executor.run_until_parked();
16882    cx.assert_state_with_diff(
16883        r#"
16884        - use some::mod1;
16885          use some::mod2;
16886
16887          const A: u32 = 42;
16888        - const B: u32 = 42;
16889          const C: u32 = 42;
16890
16891          fn main(ˇ) {
16892        -     println!("hello");
16893        +     //println!("hello");
16894
16895              println!("world");
16896        +     //
16897        +     //
16898          }
16899        "#
16900        .unindent(),
16901    );
16902
16903    cx.set_head_text("new diff base!");
16904    executor.run_until_parked();
16905    cx.assert_state_with_diff(
16906        r#"
16907        - new diff base!
16908        + use some::mod2;
16909        +
16910        + const A: u32 = 42;
16911        + const C: u32 = 42;
16912        +
16913        + fn main(ˇ) {
16914        +     //println!("hello");
16915        +
16916        +     println!("world");
16917        +     //
16918        +     //
16919        + }
16920        "#
16921        .unindent(),
16922    );
16923}
16924
16925#[gpui::test]
16926async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16927    init_test(cx, |_| {});
16928
16929    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16930    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16931    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16932    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16933    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16934    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16935
16936    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16937    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16938    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16939
16940    let multi_buffer = cx.new(|cx| {
16941        let mut multibuffer = MultiBuffer::new(ReadWrite);
16942        multibuffer.push_excerpts(
16943            buffer_1.clone(),
16944            [
16945                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16946                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16947                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16948            ],
16949            cx,
16950        );
16951        multibuffer.push_excerpts(
16952            buffer_2.clone(),
16953            [
16954                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16955                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16956                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16957            ],
16958            cx,
16959        );
16960        multibuffer.push_excerpts(
16961            buffer_3.clone(),
16962            [
16963                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16964                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16965                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16966            ],
16967            cx,
16968        );
16969        multibuffer
16970    });
16971
16972    let editor =
16973        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16974    editor
16975        .update(cx, |editor, _window, cx| {
16976            for (buffer, diff_base) in [
16977                (buffer_1.clone(), file_1_old),
16978                (buffer_2.clone(), file_2_old),
16979                (buffer_3.clone(), file_3_old),
16980            ] {
16981                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16982                editor
16983                    .buffer
16984                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16985            }
16986        })
16987        .unwrap();
16988
16989    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16990    cx.run_until_parked();
16991
16992    cx.assert_editor_state(
16993        &"
16994            ˇaaa
16995            ccc
16996            ddd
16997
16998            ggg
16999            hhh
17000
17001
17002            lll
17003            mmm
17004            NNN
17005
17006            qqq
17007            rrr
17008
17009            uuu
17010            111
17011            222
17012            333
17013
17014            666
17015            777
17016
17017            000
17018            !!!"
17019        .unindent(),
17020    );
17021
17022    cx.update_editor(|editor, window, cx| {
17023        editor.select_all(&SelectAll, window, cx);
17024        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17025    });
17026    cx.executor().run_until_parked();
17027
17028    cx.assert_state_with_diff(
17029        "
17030            «aaa
17031          - bbb
17032            ccc
17033            ddd
17034
17035            ggg
17036            hhh
17037
17038
17039            lll
17040            mmm
17041          - nnn
17042          + NNN
17043
17044            qqq
17045            rrr
17046
17047            uuu
17048            111
17049            222
17050            333
17051
17052          + 666
17053            777
17054
17055            000
17056            !!!ˇ»"
17057            .unindent(),
17058    );
17059}
17060
17061#[gpui::test]
17062async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17063    init_test(cx, |_| {});
17064
17065    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17066    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17067
17068    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17069    let multi_buffer = cx.new(|cx| {
17070        let mut multibuffer = MultiBuffer::new(ReadWrite);
17071        multibuffer.push_excerpts(
17072            buffer.clone(),
17073            [
17074                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17075                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17076                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17077            ],
17078            cx,
17079        );
17080        multibuffer
17081    });
17082
17083    let editor =
17084        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17085    editor
17086        .update(cx, |editor, _window, cx| {
17087            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17088            editor
17089                .buffer
17090                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17091        })
17092        .unwrap();
17093
17094    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17095    cx.run_until_parked();
17096
17097    cx.update_editor(|editor, window, cx| {
17098        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17099    });
17100    cx.executor().run_until_parked();
17101
17102    // When the start of a hunk coincides with the start of its excerpt,
17103    // the hunk is expanded. When the start of a a hunk is earlier than
17104    // the start of its excerpt, the hunk is not expanded.
17105    cx.assert_state_with_diff(
17106        "
17107            ˇaaa
17108          - bbb
17109          + BBB
17110
17111          - ddd
17112          - eee
17113          + DDD
17114          + EEE
17115            fff
17116
17117            iii
17118        "
17119        .unindent(),
17120    );
17121}
17122
17123#[gpui::test]
17124async fn test_edits_around_expanded_insertion_hunks(
17125    executor: BackgroundExecutor,
17126    cx: &mut TestAppContext,
17127) {
17128    init_test(cx, |_| {});
17129
17130    let mut cx = EditorTestContext::new(cx).await;
17131
17132    let diff_base = r#"
17133        use some::mod1;
17134        use some::mod2;
17135
17136        const A: u32 = 42;
17137
17138        fn main() {
17139            println!("hello");
17140
17141            println!("world");
17142        }
17143        "#
17144    .unindent();
17145    executor.run_until_parked();
17146    cx.set_state(
17147        &r#"
17148        use some::mod1;
17149        use some::mod2;
17150
17151        const A: u32 = 42;
17152        const B: u32 = 42;
17153        const C: u32 = 42;
17154        ˇ
17155
17156        fn main() {
17157            println!("hello");
17158
17159            println!("world");
17160        }
17161        "#
17162        .unindent(),
17163    );
17164
17165    cx.set_head_text(&diff_base);
17166    executor.run_until_parked();
17167
17168    cx.update_editor(|editor, window, cx| {
17169        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17170    });
17171    executor.run_until_parked();
17172
17173    cx.assert_state_with_diff(
17174        r#"
17175        use some::mod1;
17176        use some::mod2;
17177
17178        const A: u32 = 42;
17179      + const B: u32 = 42;
17180      + const C: u32 = 42;
17181      + ˇ
17182
17183        fn main() {
17184            println!("hello");
17185
17186            println!("world");
17187        }
17188      "#
17189        .unindent(),
17190    );
17191
17192    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17193    executor.run_until_parked();
17194
17195    cx.assert_state_with_diff(
17196        r#"
17197        use some::mod1;
17198        use some::mod2;
17199
17200        const A: u32 = 42;
17201      + const B: u32 = 42;
17202      + const C: u32 = 42;
17203      + const D: u32 = 42;
17204      + ˇ
17205
17206        fn main() {
17207            println!("hello");
17208
17209            println!("world");
17210        }
17211      "#
17212        .unindent(),
17213    );
17214
17215    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17216    executor.run_until_parked();
17217
17218    cx.assert_state_with_diff(
17219        r#"
17220        use some::mod1;
17221        use some::mod2;
17222
17223        const A: u32 = 42;
17224      + const B: u32 = 42;
17225      + const C: u32 = 42;
17226      + const D: u32 = 42;
17227      + const E: u32 = 42;
17228      + ˇ
17229
17230        fn main() {
17231            println!("hello");
17232
17233            println!("world");
17234        }
17235      "#
17236        .unindent(),
17237    );
17238
17239    cx.update_editor(|editor, window, cx| {
17240        editor.delete_line(&DeleteLine, window, cx);
17241    });
17242    executor.run_until_parked();
17243
17244    cx.assert_state_with_diff(
17245        r#"
17246        use some::mod1;
17247        use some::mod2;
17248
17249        const A: u32 = 42;
17250      + const B: u32 = 42;
17251      + const C: u32 = 42;
17252      + const D: u32 = 42;
17253      + const E: u32 = 42;
17254        ˇ
17255        fn main() {
17256            println!("hello");
17257
17258            println!("world");
17259        }
17260      "#
17261        .unindent(),
17262    );
17263
17264    cx.update_editor(|editor, window, cx| {
17265        editor.move_up(&MoveUp, window, cx);
17266        editor.delete_line(&DeleteLine, window, cx);
17267        editor.move_up(&MoveUp, window, cx);
17268        editor.delete_line(&DeleteLine, window, cx);
17269        editor.move_up(&MoveUp, window, cx);
17270        editor.delete_line(&DeleteLine, window, cx);
17271    });
17272    executor.run_until_parked();
17273    cx.assert_state_with_diff(
17274        r#"
17275        use some::mod1;
17276        use some::mod2;
17277
17278        const A: u32 = 42;
17279      + const B: u32 = 42;
17280        ˇ
17281        fn main() {
17282            println!("hello");
17283
17284            println!("world");
17285        }
17286      "#
17287        .unindent(),
17288    );
17289
17290    cx.update_editor(|editor, window, cx| {
17291        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17292        editor.delete_line(&DeleteLine, window, cx);
17293    });
17294    executor.run_until_parked();
17295    cx.assert_state_with_diff(
17296        r#"
17297        ˇ
17298        fn main() {
17299            println!("hello");
17300
17301            println!("world");
17302        }
17303      "#
17304        .unindent(),
17305    );
17306}
17307
17308#[gpui::test]
17309async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17310    init_test(cx, |_| {});
17311
17312    let mut cx = EditorTestContext::new(cx).await;
17313    cx.set_head_text(indoc! { "
17314        one
17315        two
17316        three
17317        four
17318        five
17319        "
17320    });
17321    cx.set_state(indoc! { "
17322        one
17323        ˇthree
17324        five
17325    "});
17326    cx.run_until_parked();
17327    cx.update_editor(|editor, window, cx| {
17328        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17329    });
17330    cx.assert_state_with_diff(
17331        indoc! { "
17332        one
17333      - two
17334        ˇthree
17335      - four
17336        five
17337    "}
17338        .to_string(),
17339    );
17340    cx.update_editor(|editor, window, cx| {
17341        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17342    });
17343
17344    cx.assert_state_with_diff(
17345        indoc! { "
17346        one
17347        ˇthree
17348        five
17349    "}
17350        .to_string(),
17351    );
17352
17353    cx.set_state(indoc! { "
17354        one
17355        ˇTWO
17356        three
17357        four
17358        five
17359    "});
17360    cx.run_until_parked();
17361    cx.update_editor(|editor, window, cx| {
17362        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17363    });
17364
17365    cx.assert_state_with_diff(
17366        indoc! { "
17367            one
17368          - two
17369          + ˇTWO
17370            three
17371            four
17372            five
17373        "}
17374        .to_string(),
17375    );
17376    cx.update_editor(|editor, window, cx| {
17377        editor.move_up(&Default::default(), window, cx);
17378        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17379    });
17380    cx.assert_state_with_diff(
17381        indoc! { "
17382            one
17383            ˇTWO
17384            three
17385            four
17386            five
17387        "}
17388        .to_string(),
17389    );
17390}
17391
17392#[gpui::test]
17393async fn test_edits_around_expanded_deletion_hunks(
17394    executor: BackgroundExecutor,
17395    cx: &mut TestAppContext,
17396) {
17397    init_test(cx, |_| {});
17398
17399    let mut cx = EditorTestContext::new(cx).await;
17400
17401    let diff_base = r#"
17402        use some::mod1;
17403        use some::mod2;
17404
17405        const A: u32 = 42;
17406        const B: u32 = 42;
17407        const C: u32 = 42;
17408
17409
17410        fn main() {
17411            println!("hello");
17412
17413            println!("world");
17414        }
17415    "#
17416    .unindent();
17417    executor.run_until_parked();
17418    cx.set_state(
17419        &r#"
17420        use some::mod1;
17421        use some::mod2;
17422
17423        ˇconst B: u32 = 42;
17424        const C: u32 = 42;
17425
17426
17427        fn main() {
17428            println!("hello");
17429
17430            println!("world");
17431        }
17432        "#
17433        .unindent(),
17434    );
17435
17436    cx.set_head_text(&diff_base);
17437    executor.run_until_parked();
17438
17439    cx.update_editor(|editor, window, cx| {
17440        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17441    });
17442    executor.run_until_parked();
17443
17444    cx.assert_state_with_diff(
17445        r#"
17446        use some::mod1;
17447        use some::mod2;
17448
17449      - const A: u32 = 42;
17450        ˇconst B: u32 = 42;
17451        const C: u32 = 42;
17452
17453
17454        fn main() {
17455            println!("hello");
17456
17457            println!("world");
17458        }
17459      "#
17460        .unindent(),
17461    );
17462
17463    cx.update_editor(|editor, window, cx| {
17464        editor.delete_line(&DeleteLine, window, cx);
17465    });
17466    executor.run_until_parked();
17467    cx.assert_state_with_diff(
17468        r#"
17469        use some::mod1;
17470        use some::mod2;
17471
17472      - const A: u32 = 42;
17473      - const B: u32 = 42;
17474        ˇconst C: u32 = 42;
17475
17476
17477        fn main() {
17478            println!("hello");
17479
17480            println!("world");
17481        }
17482      "#
17483        .unindent(),
17484    );
17485
17486    cx.update_editor(|editor, window, cx| {
17487        editor.delete_line(&DeleteLine, window, cx);
17488    });
17489    executor.run_until_parked();
17490    cx.assert_state_with_diff(
17491        r#"
17492        use some::mod1;
17493        use some::mod2;
17494
17495      - const A: u32 = 42;
17496      - const B: u32 = 42;
17497      - const C: u32 = 42;
17498        ˇ
17499
17500        fn main() {
17501            println!("hello");
17502
17503            println!("world");
17504        }
17505      "#
17506        .unindent(),
17507    );
17508
17509    cx.update_editor(|editor, window, cx| {
17510        editor.handle_input("replacement", window, cx);
17511    });
17512    executor.run_until_parked();
17513    cx.assert_state_with_diff(
17514        r#"
17515        use some::mod1;
17516        use some::mod2;
17517
17518      - const A: u32 = 42;
17519      - const B: u32 = 42;
17520      - const C: u32 = 42;
17521      -
17522      + replacementˇ
17523
17524        fn main() {
17525            println!("hello");
17526
17527            println!("world");
17528        }
17529      "#
17530        .unindent(),
17531    );
17532}
17533
17534#[gpui::test]
17535async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17536    init_test(cx, |_| {});
17537
17538    let mut cx = EditorTestContext::new(cx).await;
17539
17540    let base_text = r#"
17541        one
17542        two
17543        three
17544        four
17545        five
17546    "#
17547    .unindent();
17548    executor.run_until_parked();
17549    cx.set_state(
17550        &r#"
17551        one
17552        two
17553        fˇour
17554        five
17555        "#
17556        .unindent(),
17557    );
17558
17559    cx.set_head_text(&base_text);
17560    executor.run_until_parked();
17561
17562    cx.update_editor(|editor, window, cx| {
17563        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17564    });
17565    executor.run_until_parked();
17566
17567    cx.assert_state_with_diff(
17568        r#"
17569          one
17570          two
17571        - three
17572          fˇour
17573          five
17574        "#
17575        .unindent(),
17576    );
17577
17578    cx.update_editor(|editor, window, cx| {
17579        editor.backspace(&Backspace, window, cx);
17580        editor.backspace(&Backspace, window, cx);
17581    });
17582    executor.run_until_parked();
17583    cx.assert_state_with_diff(
17584        r#"
17585          one
17586          two
17587        - threeˇ
17588        - four
17589        + our
17590          five
17591        "#
17592        .unindent(),
17593    );
17594}
17595
17596#[gpui::test]
17597async fn test_edit_after_expanded_modification_hunk(
17598    executor: BackgroundExecutor,
17599    cx: &mut TestAppContext,
17600) {
17601    init_test(cx, |_| {});
17602
17603    let mut cx = EditorTestContext::new(cx).await;
17604
17605    let diff_base = r#"
17606        use some::mod1;
17607        use some::mod2;
17608
17609        const A: u32 = 42;
17610        const B: u32 = 42;
17611        const C: u32 = 42;
17612        const D: u32 = 42;
17613
17614
17615        fn main() {
17616            println!("hello");
17617
17618            println!("world");
17619        }"#
17620    .unindent();
17621
17622    cx.set_state(
17623        &r#"
17624        use some::mod1;
17625        use some::mod2;
17626
17627        const A: u32 = 42;
17628        const B: u32 = 42;
17629        const C: u32 = 43ˇ
17630        const D: u32 = 42;
17631
17632
17633        fn main() {
17634            println!("hello");
17635
17636            println!("world");
17637        }"#
17638        .unindent(),
17639    );
17640
17641    cx.set_head_text(&diff_base);
17642    executor.run_until_parked();
17643    cx.update_editor(|editor, window, cx| {
17644        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17645    });
17646    executor.run_until_parked();
17647
17648    cx.assert_state_with_diff(
17649        r#"
17650        use some::mod1;
17651        use some::mod2;
17652
17653        const A: u32 = 42;
17654        const B: u32 = 42;
17655      - const C: u32 = 42;
17656      + const C: u32 = 43ˇ
17657        const D: u32 = 42;
17658
17659
17660        fn main() {
17661            println!("hello");
17662
17663            println!("world");
17664        }"#
17665        .unindent(),
17666    );
17667
17668    cx.update_editor(|editor, window, cx| {
17669        editor.handle_input("\nnew_line\n", window, cx);
17670    });
17671    executor.run_until_parked();
17672
17673    cx.assert_state_with_diff(
17674        r#"
17675        use some::mod1;
17676        use some::mod2;
17677
17678        const A: u32 = 42;
17679        const B: u32 = 42;
17680      - const C: u32 = 42;
17681      + const C: u32 = 43
17682      + new_line
17683      + ˇ
17684        const D: u32 = 42;
17685
17686
17687        fn main() {
17688            println!("hello");
17689
17690            println!("world");
17691        }"#
17692        .unindent(),
17693    );
17694}
17695
17696#[gpui::test]
17697async fn test_stage_and_unstage_added_file_hunk(
17698    executor: BackgroundExecutor,
17699    cx: &mut TestAppContext,
17700) {
17701    init_test(cx, |_| {});
17702
17703    let mut cx = EditorTestContext::new(cx).await;
17704    cx.update_editor(|editor, _, cx| {
17705        editor.set_expand_all_diff_hunks(cx);
17706    });
17707
17708    let working_copy = r#"
17709            ˇfn main() {
17710                println!("hello, world!");
17711            }
17712        "#
17713    .unindent();
17714
17715    cx.set_state(&working_copy);
17716    executor.run_until_parked();
17717
17718    cx.assert_state_with_diff(
17719        r#"
17720            + ˇfn main() {
17721            +     println!("hello, world!");
17722            + }
17723        "#
17724        .unindent(),
17725    );
17726    cx.assert_index_text(None);
17727
17728    cx.update_editor(|editor, window, cx| {
17729        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17730    });
17731    executor.run_until_parked();
17732    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17733    cx.assert_state_with_diff(
17734        r#"
17735            + ˇfn main() {
17736            +     println!("hello, world!");
17737            + }
17738        "#
17739        .unindent(),
17740    );
17741
17742    cx.update_editor(|editor, window, cx| {
17743        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17744    });
17745    executor.run_until_parked();
17746    cx.assert_index_text(None);
17747}
17748
17749async fn setup_indent_guides_editor(
17750    text: &str,
17751    cx: &mut TestAppContext,
17752) -> (BufferId, EditorTestContext) {
17753    init_test(cx, |_| {});
17754
17755    let mut cx = EditorTestContext::new(cx).await;
17756
17757    let buffer_id = cx.update_editor(|editor, window, cx| {
17758        editor.set_text(text, window, cx);
17759        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17760
17761        buffer_ids[0]
17762    });
17763
17764    (buffer_id, cx)
17765}
17766
17767fn assert_indent_guides(
17768    range: Range<u32>,
17769    expected: Vec<IndentGuide>,
17770    active_indices: Option<Vec<usize>>,
17771    cx: &mut EditorTestContext,
17772) {
17773    let indent_guides = cx.update_editor(|editor, window, cx| {
17774        let snapshot = editor.snapshot(window, cx).display_snapshot;
17775        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17776            editor,
17777            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17778            true,
17779            &snapshot,
17780            cx,
17781        );
17782
17783        indent_guides.sort_by(|a, b| {
17784            a.depth.cmp(&b.depth).then(
17785                a.start_row
17786                    .cmp(&b.start_row)
17787                    .then(a.end_row.cmp(&b.end_row)),
17788            )
17789        });
17790        indent_guides
17791    });
17792
17793    if let Some(expected) = active_indices {
17794        let active_indices = cx.update_editor(|editor, window, cx| {
17795            let snapshot = editor.snapshot(window, cx).display_snapshot;
17796            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17797        });
17798
17799        assert_eq!(
17800            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17801            expected,
17802            "Active indent guide indices do not match"
17803        );
17804    }
17805
17806    assert_eq!(indent_guides, expected, "Indent guides do not match");
17807}
17808
17809fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17810    IndentGuide {
17811        buffer_id,
17812        start_row: MultiBufferRow(start_row),
17813        end_row: MultiBufferRow(end_row),
17814        depth,
17815        tab_size: 4,
17816        settings: IndentGuideSettings {
17817            enabled: true,
17818            line_width: 1,
17819            active_line_width: 1,
17820            ..Default::default()
17821        },
17822    }
17823}
17824
17825#[gpui::test]
17826async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17827    let (buffer_id, mut cx) = setup_indent_guides_editor(
17828        &"
17829        fn main() {
17830            let a = 1;
17831        }"
17832        .unindent(),
17833        cx,
17834    )
17835    .await;
17836
17837    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17838}
17839
17840#[gpui::test]
17841async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17842    let (buffer_id, mut cx) = setup_indent_guides_editor(
17843        &"
17844        fn main() {
17845            let a = 1;
17846            let b = 2;
17847        }"
17848        .unindent(),
17849        cx,
17850    )
17851    .await;
17852
17853    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17854}
17855
17856#[gpui::test]
17857async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17858    let (buffer_id, mut cx) = setup_indent_guides_editor(
17859        &"
17860        fn main() {
17861            let a = 1;
17862            if a == 3 {
17863                let b = 2;
17864            } else {
17865                let c = 3;
17866            }
17867        }"
17868        .unindent(),
17869        cx,
17870    )
17871    .await;
17872
17873    assert_indent_guides(
17874        0..8,
17875        vec![
17876            indent_guide(buffer_id, 1, 6, 0),
17877            indent_guide(buffer_id, 3, 3, 1),
17878            indent_guide(buffer_id, 5, 5, 1),
17879        ],
17880        None,
17881        &mut cx,
17882    );
17883}
17884
17885#[gpui::test]
17886async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17887    let (buffer_id, mut cx) = setup_indent_guides_editor(
17888        &"
17889        fn main() {
17890            let a = 1;
17891                let b = 2;
17892            let c = 3;
17893        }"
17894        .unindent(),
17895        cx,
17896    )
17897    .await;
17898
17899    assert_indent_guides(
17900        0..5,
17901        vec![
17902            indent_guide(buffer_id, 1, 3, 0),
17903            indent_guide(buffer_id, 2, 2, 1),
17904        ],
17905        None,
17906        &mut cx,
17907    );
17908}
17909
17910#[gpui::test]
17911async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17912    let (buffer_id, mut cx) = setup_indent_guides_editor(
17913        &"
17914        fn main() {
17915            let a = 1;
17916
17917            let c = 3;
17918        }"
17919        .unindent(),
17920        cx,
17921    )
17922    .await;
17923
17924    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17925}
17926
17927#[gpui::test]
17928async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17929    let (buffer_id, mut cx) = setup_indent_guides_editor(
17930        &"
17931        fn main() {
17932            let a = 1;
17933
17934            let c = 3;
17935
17936            if a == 3 {
17937                let b = 2;
17938            } else {
17939                let c = 3;
17940            }
17941        }"
17942        .unindent(),
17943        cx,
17944    )
17945    .await;
17946
17947    assert_indent_guides(
17948        0..11,
17949        vec![
17950            indent_guide(buffer_id, 1, 9, 0),
17951            indent_guide(buffer_id, 6, 6, 1),
17952            indent_guide(buffer_id, 8, 8, 1),
17953        ],
17954        None,
17955        &mut cx,
17956    );
17957}
17958
17959#[gpui::test]
17960async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17961    let (buffer_id, mut cx) = setup_indent_guides_editor(
17962        &"
17963        fn main() {
17964            let a = 1;
17965
17966            let c = 3;
17967
17968            if a == 3 {
17969                let b = 2;
17970            } else {
17971                let c = 3;
17972            }
17973        }"
17974        .unindent(),
17975        cx,
17976    )
17977    .await;
17978
17979    assert_indent_guides(
17980        1..11,
17981        vec![
17982            indent_guide(buffer_id, 1, 9, 0),
17983            indent_guide(buffer_id, 6, 6, 1),
17984            indent_guide(buffer_id, 8, 8, 1),
17985        ],
17986        None,
17987        &mut cx,
17988    );
17989}
17990
17991#[gpui::test]
17992async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17993    let (buffer_id, mut cx) = setup_indent_guides_editor(
17994        &"
17995        fn main() {
17996            let a = 1;
17997
17998            let c = 3;
17999
18000            if a == 3 {
18001                let b = 2;
18002            } else {
18003                let c = 3;
18004            }
18005        }"
18006        .unindent(),
18007        cx,
18008    )
18009    .await;
18010
18011    assert_indent_guides(
18012        1..10,
18013        vec![
18014            indent_guide(buffer_id, 1, 9, 0),
18015            indent_guide(buffer_id, 6, 6, 1),
18016            indent_guide(buffer_id, 8, 8, 1),
18017        ],
18018        None,
18019        &mut cx,
18020    );
18021}
18022
18023#[gpui::test]
18024async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18025    let (buffer_id, mut cx) = setup_indent_guides_editor(
18026        &"
18027        fn main() {
18028            if a {
18029                b(
18030                    c,
18031                    d,
18032                )
18033            } else {
18034                e(
18035                    f
18036                )
18037            }
18038        }"
18039        .unindent(),
18040        cx,
18041    )
18042    .await;
18043
18044    assert_indent_guides(
18045        0..11,
18046        vec![
18047            indent_guide(buffer_id, 1, 10, 0),
18048            indent_guide(buffer_id, 2, 5, 1),
18049            indent_guide(buffer_id, 7, 9, 1),
18050            indent_guide(buffer_id, 3, 4, 2),
18051            indent_guide(buffer_id, 8, 8, 2),
18052        ],
18053        None,
18054        &mut cx,
18055    );
18056
18057    cx.update_editor(|editor, window, cx| {
18058        editor.fold_at(MultiBufferRow(2), window, cx);
18059        assert_eq!(
18060            editor.display_text(cx),
18061            "
18062            fn main() {
18063                if a {
18064                    b(⋯
18065                    )
18066                } else {
18067                    e(
18068                        f
18069                    )
18070                }
18071            }"
18072            .unindent()
18073        );
18074    });
18075
18076    assert_indent_guides(
18077        0..11,
18078        vec![
18079            indent_guide(buffer_id, 1, 10, 0),
18080            indent_guide(buffer_id, 2, 5, 1),
18081            indent_guide(buffer_id, 7, 9, 1),
18082            indent_guide(buffer_id, 8, 8, 2),
18083        ],
18084        None,
18085        &mut cx,
18086    );
18087}
18088
18089#[gpui::test]
18090async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18091    let (buffer_id, mut cx) = setup_indent_guides_editor(
18092        &"
18093        block1
18094            block2
18095                block3
18096                    block4
18097            block2
18098        block1
18099        block1"
18100            .unindent(),
18101        cx,
18102    )
18103    .await;
18104
18105    assert_indent_guides(
18106        1..10,
18107        vec![
18108            indent_guide(buffer_id, 1, 4, 0),
18109            indent_guide(buffer_id, 2, 3, 1),
18110            indent_guide(buffer_id, 3, 3, 2),
18111        ],
18112        None,
18113        &mut cx,
18114    );
18115}
18116
18117#[gpui::test]
18118async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18119    let (buffer_id, mut cx) = setup_indent_guides_editor(
18120        &"
18121        block1
18122            block2
18123                block3
18124
18125        block1
18126        block1"
18127            .unindent(),
18128        cx,
18129    )
18130    .await;
18131
18132    assert_indent_guides(
18133        0..6,
18134        vec![
18135            indent_guide(buffer_id, 1, 2, 0),
18136            indent_guide(buffer_id, 2, 2, 1),
18137        ],
18138        None,
18139        &mut cx,
18140    );
18141}
18142
18143#[gpui::test]
18144async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18145    let (buffer_id, mut cx) = setup_indent_guides_editor(
18146        &"
18147        function component() {
18148        \treturn (
18149        \t\t\t
18150        \t\t<div>
18151        \t\t\t<abc></abc>
18152        \t\t</div>
18153        \t)
18154        }"
18155        .unindent(),
18156        cx,
18157    )
18158    .await;
18159
18160    assert_indent_guides(
18161        0..8,
18162        vec![
18163            indent_guide(buffer_id, 1, 6, 0),
18164            indent_guide(buffer_id, 2, 5, 1),
18165            indent_guide(buffer_id, 4, 4, 2),
18166        ],
18167        None,
18168        &mut cx,
18169    );
18170}
18171
18172#[gpui::test]
18173async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18174    let (buffer_id, mut cx) = setup_indent_guides_editor(
18175        &"
18176        function component() {
18177        \treturn (
18178        \t
18179        \t\t<div>
18180        \t\t\t<abc></abc>
18181        \t\t</div>
18182        \t)
18183        }"
18184        .unindent(),
18185        cx,
18186    )
18187    .await;
18188
18189    assert_indent_guides(
18190        0..8,
18191        vec![
18192            indent_guide(buffer_id, 1, 6, 0),
18193            indent_guide(buffer_id, 2, 5, 1),
18194            indent_guide(buffer_id, 4, 4, 2),
18195        ],
18196        None,
18197        &mut cx,
18198    );
18199}
18200
18201#[gpui::test]
18202async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18203    let (buffer_id, mut cx) = setup_indent_guides_editor(
18204        &"
18205        block1
18206
18207
18208
18209            block2
18210        "
18211        .unindent(),
18212        cx,
18213    )
18214    .await;
18215
18216    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18217}
18218
18219#[gpui::test]
18220async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18221    let (buffer_id, mut cx) = setup_indent_guides_editor(
18222        &"
18223        def a:
18224        \tb = 3
18225        \tif True:
18226        \t\tc = 4
18227        \t\td = 5
18228        \tprint(b)
18229        "
18230        .unindent(),
18231        cx,
18232    )
18233    .await;
18234
18235    assert_indent_guides(
18236        0..6,
18237        vec![
18238            indent_guide(buffer_id, 1, 5, 0),
18239            indent_guide(buffer_id, 3, 4, 1),
18240        ],
18241        None,
18242        &mut cx,
18243    );
18244}
18245
18246#[gpui::test]
18247async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18248    let (buffer_id, mut cx) = setup_indent_guides_editor(
18249        &"
18250    fn main() {
18251        let a = 1;
18252    }"
18253        .unindent(),
18254        cx,
18255    )
18256    .await;
18257
18258    cx.update_editor(|editor, window, cx| {
18259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18260            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18261        });
18262    });
18263
18264    assert_indent_guides(
18265        0..3,
18266        vec![indent_guide(buffer_id, 1, 1, 0)],
18267        Some(vec![0]),
18268        &mut cx,
18269    );
18270}
18271
18272#[gpui::test]
18273async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18274    let (buffer_id, mut cx) = setup_indent_guides_editor(
18275        &"
18276    fn main() {
18277        if 1 == 2 {
18278            let a = 1;
18279        }
18280    }"
18281        .unindent(),
18282        cx,
18283    )
18284    .await;
18285
18286    cx.update_editor(|editor, window, cx| {
18287        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18288            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18289        });
18290    });
18291
18292    assert_indent_guides(
18293        0..4,
18294        vec![
18295            indent_guide(buffer_id, 1, 3, 0),
18296            indent_guide(buffer_id, 2, 2, 1),
18297        ],
18298        Some(vec![1]),
18299        &mut cx,
18300    );
18301
18302    cx.update_editor(|editor, window, cx| {
18303        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18304            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18305        });
18306    });
18307
18308    assert_indent_guides(
18309        0..4,
18310        vec![
18311            indent_guide(buffer_id, 1, 3, 0),
18312            indent_guide(buffer_id, 2, 2, 1),
18313        ],
18314        Some(vec![1]),
18315        &mut cx,
18316    );
18317
18318    cx.update_editor(|editor, window, cx| {
18319        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18320            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18321        });
18322    });
18323
18324    assert_indent_guides(
18325        0..4,
18326        vec![
18327            indent_guide(buffer_id, 1, 3, 0),
18328            indent_guide(buffer_id, 2, 2, 1),
18329        ],
18330        Some(vec![0]),
18331        &mut cx,
18332    );
18333}
18334
18335#[gpui::test]
18336async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18337    let (buffer_id, mut cx) = setup_indent_guides_editor(
18338        &"
18339    fn main() {
18340        let a = 1;
18341
18342        let b = 2;
18343    }"
18344        .unindent(),
18345        cx,
18346    )
18347    .await;
18348
18349    cx.update_editor(|editor, window, cx| {
18350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18351            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18352        });
18353    });
18354
18355    assert_indent_guides(
18356        0..5,
18357        vec![indent_guide(buffer_id, 1, 3, 0)],
18358        Some(vec![0]),
18359        &mut cx,
18360    );
18361}
18362
18363#[gpui::test]
18364async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18365    let (buffer_id, mut cx) = setup_indent_guides_editor(
18366        &"
18367    def m:
18368        a = 1
18369        pass"
18370            .unindent(),
18371        cx,
18372    )
18373    .await;
18374
18375    cx.update_editor(|editor, window, cx| {
18376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18377            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18378        });
18379    });
18380
18381    assert_indent_guides(
18382        0..3,
18383        vec![indent_guide(buffer_id, 1, 2, 0)],
18384        Some(vec![0]),
18385        &mut cx,
18386    );
18387}
18388
18389#[gpui::test]
18390async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18391    init_test(cx, |_| {});
18392    let mut cx = EditorTestContext::new(cx).await;
18393    let text = indoc! {
18394        "
18395        impl A {
18396            fn b() {
18397                0;
18398                3;
18399                5;
18400                6;
18401                7;
18402            }
18403        }
18404        "
18405    };
18406    let base_text = indoc! {
18407        "
18408        impl A {
18409            fn b() {
18410                0;
18411                1;
18412                2;
18413                3;
18414                4;
18415            }
18416            fn c() {
18417                5;
18418                6;
18419                7;
18420            }
18421        }
18422        "
18423    };
18424
18425    cx.update_editor(|editor, window, cx| {
18426        editor.set_text(text, window, cx);
18427
18428        editor.buffer().update(cx, |multibuffer, cx| {
18429            let buffer = multibuffer.as_singleton().unwrap();
18430            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18431
18432            multibuffer.set_all_diff_hunks_expanded(cx);
18433            multibuffer.add_diff(diff, cx);
18434
18435            buffer.read(cx).remote_id()
18436        })
18437    });
18438    cx.run_until_parked();
18439
18440    cx.assert_state_with_diff(
18441        indoc! { "
18442          impl A {
18443              fn b() {
18444                  0;
18445        -         1;
18446        -         2;
18447                  3;
18448        -         4;
18449        -     }
18450        -     fn c() {
18451                  5;
18452                  6;
18453                  7;
18454              }
18455          }
18456          ˇ"
18457        }
18458        .to_string(),
18459    );
18460
18461    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18462        editor
18463            .snapshot(window, cx)
18464            .buffer_snapshot
18465            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18466            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18467            .collect::<Vec<_>>()
18468    });
18469    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18470    assert_eq!(
18471        actual_guides,
18472        vec![
18473            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18474            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18475            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18476        ]
18477    );
18478}
18479
18480#[gpui::test]
18481async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18482    init_test(cx, |_| {});
18483    let mut cx = EditorTestContext::new(cx).await;
18484
18485    let diff_base = r#"
18486        a
18487        b
18488        c
18489        "#
18490    .unindent();
18491
18492    cx.set_state(
18493        &r#"
18494        ˇA
18495        b
18496        C
18497        "#
18498        .unindent(),
18499    );
18500    cx.set_head_text(&diff_base);
18501    cx.update_editor(|editor, window, cx| {
18502        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18503    });
18504    executor.run_until_parked();
18505
18506    let both_hunks_expanded = r#"
18507        - a
18508        + ˇA
18509          b
18510        - c
18511        + C
18512        "#
18513    .unindent();
18514
18515    cx.assert_state_with_diff(both_hunks_expanded.clone());
18516
18517    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18518        let snapshot = editor.snapshot(window, cx);
18519        let hunks = editor
18520            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18521            .collect::<Vec<_>>();
18522        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18523        let buffer_id = hunks[0].buffer_id;
18524        hunks
18525            .into_iter()
18526            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18527            .collect::<Vec<_>>()
18528    });
18529    assert_eq!(hunk_ranges.len(), 2);
18530
18531    cx.update_editor(|editor, _, cx| {
18532        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18533    });
18534    executor.run_until_parked();
18535
18536    let second_hunk_expanded = r#"
18537          ˇA
18538          b
18539        - c
18540        + C
18541        "#
18542    .unindent();
18543
18544    cx.assert_state_with_diff(second_hunk_expanded);
18545
18546    cx.update_editor(|editor, _, cx| {
18547        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18548    });
18549    executor.run_until_parked();
18550
18551    cx.assert_state_with_diff(both_hunks_expanded.clone());
18552
18553    cx.update_editor(|editor, _, cx| {
18554        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18555    });
18556    executor.run_until_parked();
18557
18558    let first_hunk_expanded = r#"
18559        - a
18560        + ˇA
18561          b
18562          C
18563        "#
18564    .unindent();
18565
18566    cx.assert_state_with_diff(first_hunk_expanded);
18567
18568    cx.update_editor(|editor, _, cx| {
18569        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18570    });
18571    executor.run_until_parked();
18572
18573    cx.assert_state_with_diff(both_hunks_expanded);
18574
18575    cx.set_state(
18576        &r#"
18577        ˇA
18578        b
18579        "#
18580        .unindent(),
18581    );
18582    cx.run_until_parked();
18583
18584    // TODO this cursor position seems bad
18585    cx.assert_state_with_diff(
18586        r#"
18587        - ˇa
18588        + A
18589          b
18590        "#
18591        .unindent(),
18592    );
18593
18594    cx.update_editor(|editor, window, cx| {
18595        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18596    });
18597
18598    cx.assert_state_with_diff(
18599        r#"
18600            - ˇa
18601            + A
18602              b
18603            - c
18604            "#
18605        .unindent(),
18606    );
18607
18608    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18609        let snapshot = editor.snapshot(window, cx);
18610        let hunks = editor
18611            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18612            .collect::<Vec<_>>();
18613        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18614        let buffer_id = hunks[0].buffer_id;
18615        hunks
18616            .into_iter()
18617            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18618            .collect::<Vec<_>>()
18619    });
18620    assert_eq!(hunk_ranges.len(), 2);
18621
18622    cx.update_editor(|editor, _, cx| {
18623        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18624    });
18625    executor.run_until_parked();
18626
18627    cx.assert_state_with_diff(
18628        r#"
18629        - ˇa
18630        + A
18631          b
18632        "#
18633        .unindent(),
18634    );
18635}
18636
18637#[gpui::test]
18638async fn test_toggle_deletion_hunk_at_start_of_file(
18639    executor: BackgroundExecutor,
18640    cx: &mut TestAppContext,
18641) {
18642    init_test(cx, |_| {});
18643    let mut cx = EditorTestContext::new(cx).await;
18644
18645    let diff_base = r#"
18646        a
18647        b
18648        c
18649        "#
18650    .unindent();
18651
18652    cx.set_state(
18653        &r#"
18654        ˇb
18655        c
18656        "#
18657        .unindent(),
18658    );
18659    cx.set_head_text(&diff_base);
18660    cx.update_editor(|editor, window, cx| {
18661        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18662    });
18663    executor.run_until_parked();
18664
18665    let hunk_expanded = r#"
18666        - a
18667          ˇb
18668          c
18669        "#
18670    .unindent();
18671
18672    cx.assert_state_with_diff(hunk_expanded.clone());
18673
18674    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18675        let snapshot = editor.snapshot(window, cx);
18676        let hunks = editor
18677            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18678            .collect::<Vec<_>>();
18679        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18680        let buffer_id = hunks[0].buffer_id;
18681        hunks
18682            .into_iter()
18683            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18684            .collect::<Vec<_>>()
18685    });
18686    assert_eq!(hunk_ranges.len(), 1);
18687
18688    cx.update_editor(|editor, _, cx| {
18689        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18690    });
18691    executor.run_until_parked();
18692
18693    let hunk_collapsed = r#"
18694          ˇb
18695          c
18696        "#
18697    .unindent();
18698
18699    cx.assert_state_with_diff(hunk_collapsed);
18700
18701    cx.update_editor(|editor, _, cx| {
18702        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18703    });
18704    executor.run_until_parked();
18705
18706    cx.assert_state_with_diff(hunk_expanded.clone());
18707}
18708
18709#[gpui::test]
18710async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18711    init_test(cx, |_| {});
18712
18713    let fs = FakeFs::new(cx.executor());
18714    fs.insert_tree(
18715        path!("/test"),
18716        json!({
18717            ".git": {},
18718            "file-1": "ONE\n",
18719            "file-2": "TWO\n",
18720            "file-3": "THREE\n",
18721        }),
18722    )
18723    .await;
18724
18725    fs.set_head_for_repo(
18726        path!("/test/.git").as_ref(),
18727        &[
18728            ("file-1".into(), "one\n".into()),
18729            ("file-2".into(), "two\n".into()),
18730            ("file-3".into(), "three\n".into()),
18731        ],
18732        "deadbeef",
18733    );
18734
18735    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18736    let mut buffers = vec![];
18737    for i in 1..=3 {
18738        let buffer = project
18739            .update(cx, |project, cx| {
18740                let path = format!(path!("/test/file-{}"), i);
18741                project.open_local_buffer(path, cx)
18742            })
18743            .await
18744            .unwrap();
18745        buffers.push(buffer);
18746    }
18747
18748    let multibuffer = cx.new(|cx| {
18749        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18750        multibuffer.set_all_diff_hunks_expanded(cx);
18751        for buffer in &buffers {
18752            let snapshot = buffer.read(cx).snapshot();
18753            multibuffer.set_excerpts_for_path(
18754                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18755                buffer.clone(),
18756                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18757                DEFAULT_MULTIBUFFER_CONTEXT,
18758                cx,
18759            );
18760        }
18761        multibuffer
18762    });
18763
18764    let editor = cx.add_window(|window, cx| {
18765        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18766    });
18767    cx.run_until_parked();
18768
18769    let snapshot = editor
18770        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18771        .unwrap();
18772    let hunks = snapshot
18773        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18774        .map(|hunk| match hunk {
18775            DisplayDiffHunk::Unfolded {
18776                display_row_range, ..
18777            } => display_row_range,
18778            DisplayDiffHunk::Folded { .. } => unreachable!(),
18779        })
18780        .collect::<Vec<_>>();
18781    assert_eq!(
18782        hunks,
18783        [
18784            DisplayRow(2)..DisplayRow(4),
18785            DisplayRow(7)..DisplayRow(9),
18786            DisplayRow(12)..DisplayRow(14),
18787        ]
18788    );
18789}
18790
18791#[gpui::test]
18792async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18793    init_test(cx, |_| {});
18794
18795    let mut cx = EditorTestContext::new(cx).await;
18796    cx.set_head_text(indoc! { "
18797        one
18798        two
18799        three
18800        four
18801        five
18802        "
18803    });
18804    cx.set_index_text(indoc! { "
18805        one
18806        two
18807        three
18808        four
18809        five
18810        "
18811    });
18812    cx.set_state(indoc! {"
18813        one
18814        TWO
18815        ˇTHREE
18816        FOUR
18817        five
18818    "});
18819    cx.run_until_parked();
18820    cx.update_editor(|editor, window, cx| {
18821        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18822    });
18823    cx.run_until_parked();
18824    cx.assert_index_text(Some(indoc! {"
18825        one
18826        TWO
18827        THREE
18828        FOUR
18829        five
18830    "}));
18831    cx.set_state(indoc! { "
18832        one
18833        TWO
18834        ˇTHREE-HUNDRED
18835        FOUR
18836        five
18837    "});
18838    cx.run_until_parked();
18839    cx.update_editor(|editor, window, cx| {
18840        let snapshot = editor.snapshot(window, cx);
18841        let hunks = editor
18842            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18843            .collect::<Vec<_>>();
18844        assert_eq!(hunks.len(), 1);
18845        assert_eq!(
18846            hunks[0].status(),
18847            DiffHunkStatus {
18848                kind: DiffHunkStatusKind::Modified,
18849                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18850            }
18851        );
18852
18853        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18854    });
18855    cx.run_until_parked();
18856    cx.assert_index_text(Some(indoc! {"
18857        one
18858        TWO
18859        THREE-HUNDRED
18860        FOUR
18861        five
18862    "}));
18863}
18864
18865#[gpui::test]
18866fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18867    init_test(cx, |_| {});
18868
18869    let editor = cx.add_window(|window, cx| {
18870        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18871        build_editor(buffer, window, cx)
18872    });
18873
18874    let render_args = Arc::new(Mutex::new(None));
18875    let snapshot = editor
18876        .update(cx, |editor, window, cx| {
18877            let snapshot = editor.buffer().read(cx).snapshot(cx);
18878            let range =
18879                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18880
18881            struct RenderArgs {
18882                row: MultiBufferRow,
18883                folded: bool,
18884                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18885            }
18886
18887            let crease = Crease::inline(
18888                range,
18889                FoldPlaceholder::test(),
18890                {
18891                    let toggle_callback = render_args.clone();
18892                    move |row, folded, callback, _window, _cx| {
18893                        *toggle_callback.lock() = Some(RenderArgs {
18894                            row,
18895                            folded,
18896                            callback,
18897                        });
18898                        div()
18899                    }
18900                },
18901                |_row, _folded, _window, _cx| div(),
18902            );
18903
18904            editor.insert_creases(Some(crease), cx);
18905            let snapshot = editor.snapshot(window, cx);
18906            let _div = snapshot.render_crease_toggle(
18907                MultiBufferRow(1),
18908                false,
18909                cx.entity().clone(),
18910                window,
18911                cx,
18912            );
18913            snapshot
18914        })
18915        .unwrap();
18916
18917    let render_args = render_args.lock().take().unwrap();
18918    assert_eq!(render_args.row, MultiBufferRow(1));
18919    assert!(!render_args.folded);
18920    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18921
18922    cx.update_window(*editor, |_, window, cx| {
18923        (render_args.callback)(true, window, cx)
18924    })
18925    .unwrap();
18926    let snapshot = editor
18927        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18928        .unwrap();
18929    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18930
18931    cx.update_window(*editor, |_, window, cx| {
18932        (render_args.callback)(false, window, cx)
18933    })
18934    .unwrap();
18935    let snapshot = editor
18936        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18937        .unwrap();
18938    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18939}
18940
18941#[gpui::test]
18942async fn test_input_text(cx: &mut TestAppContext) {
18943    init_test(cx, |_| {});
18944    let mut cx = EditorTestContext::new(cx).await;
18945
18946    cx.set_state(
18947        &r#"ˇone
18948        two
18949
18950        three
18951        fourˇ
18952        five
18953
18954        siˇx"#
18955            .unindent(),
18956    );
18957
18958    cx.dispatch_action(HandleInput(String::new()));
18959    cx.assert_editor_state(
18960        &r#"ˇone
18961        two
18962
18963        three
18964        fourˇ
18965        five
18966
18967        siˇx"#
18968            .unindent(),
18969    );
18970
18971    cx.dispatch_action(HandleInput("AAAA".to_string()));
18972    cx.assert_editor_state(
18973        &r#"AAAAˇone
18974        two
18975
18976        three
18977        fourAAAAˇ
18978        five
18979
18980        siAAAAˇx"#
18981            .unindent(),
18982    );
18983}
18984
18985#[gpui::test]
18986async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18987    init_test(cx, |_| {});
18988
18989    let mut cx = EditorTestContext::new(cx).await;
18990    cx.set_state(
18991        r#"let foo = 1;
18992let foo = 2;
18993let foo = 3;
18994let fooˇ = 4;
18995let foo = 5;
18996let foo = 6;
18997let foo = 7;
18998let foo = 8;
18999let foo = 9;
19000let foo = 10;
19001let foo = 11;
19002let foo = 12;
19003let foo = 13;
19004let foo = 14;
19005let foo = 15;"#,
19006    );
19007
19008    cx.update_editor(|e, window, cx| {
19009        assert_eq!(
19010            e.next_scroll_position,
19011            NextScrollCursorCenterTopBottom::Center,
19012            "Default next scroll direction is center",
19013        );
19014
19015        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19016        assert_eq!(
19017            e.next_scroll_position,
19018            NextScrollCursorCenterTopBottom::Top,
19019            "After center, next scroll direction should be top",
19020        );
19021
19022        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19023        assert_eq!(
19024            e.next_scroll_position,
19025            NextScrollCursorCenterTopBottom::Bottom,
19026            "After top, next scroll direction should be bottom",
19027        );
19028
19029        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19030        assert_eq!(
19031            e.next_scroll_position,
19032            NextScrollCursorCenterTopBottom::Center,
19033            "After bottom, scrolling should start over",
19034        );
19035
19036        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19037        assert_eq!(
19038            e.next_scroll_position,
19039            NextScrollCursorCenterTopBottom::Top,
19040            "Scrolling continues if retriggered fast enough"
19041        );
19042    });
19043
19044    cx.executor()
19045        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19046    cx.executor().run_until_parked();
19047    cx.update_editor(|e, _, _| {
19048        assert_eq!(
19049            e.next_scroll_position,
19050            NextScrollCursorCenterTopBottom::Center,
19051            "If scrolling is not triggered fast enough, it should reset"
19052        );
19053    });
19054}
19055
19056#[gpui::test]
19057async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19058    init_test(cx, |_| {});
19059    let mut cx = EditorLspTestContext::new_rust(
19060        lsp::ServerCapabilities {
19061            definition_provider: Some(lsp::OneOf::Left(true)),
19062            references_provider: Some(lsp::OneOf::Left(true)),
19063            ..lsp::ServerCapabilities::default()
19064        },
19065        cx,
19066    )
19067    .await;
19068
19069    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19070        let go_to_definition = cx
19071            .lsp
19072            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19073                move |params, _| async move {
19074                    if empty_go_to_definition {
19075                        Ok(None)
19076                    } else {
19077                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19078                            uri: params.text_document_position_params.text_document.uri,
19079                            range: lsp::Range::new(
19080                                lsp::Position::new(4, 3),
19081                                lsp::Position::new(4, 6),
19082                            ),
19083                        })))
19084                    }
19085                },
19086            );
19087        let references = cx
19088            .lsp
19089            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19090                Ok(Some(vec![lsp::Location {
19091                    uri: params.text_document_position.text_document.uri,
19092                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19093                }]))
19094            });
19095        (go_to_definition, references)
19096    };
19097
19098    cx.set_state(
19099        &r#"fn one() {
19100            let mut a = ˇtwo();
19101        }
19102
19103        fn two() {}"#
19104            .unindent(),
19105    );
19106    set_up_lsp_handlers(false, &mut cx);
19107    let navigated = cx
19108        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19109        .await
19110        .expect("Failed to navigate to definition");
19111    assert_eq!(
19112        navigated,
19113        Navigated::Yes,
19114        "Should have navigated to definition from the GetDefinition response"
19115    );
19116    cx.assert_editor_state(
19117        &r#"fn one() {
19118            let mut a = two();
19119        }
19120
19121        fn «twoˇ»() {}"#
19122            .unindent(),
19123    );
19124
19125    let editors = cx.update_workspace(|workspace, _, cx| {
19126        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19127    });
19128    cx.update_editor(|_, _, test_editor_cx| {
19129        assert_eq!(
19130            editors.len(),
19131            1,
19132            "Initially, only one, test, editor should be open in the workspace"
19133        );
19134        assert_eq!(
19135            test_editor_cx.entity(),
19136            editors.last().expect("Asserted len is 1").clone()
19137        );
19138    });
19139
19140    set_up_lsp_handlers(true, &mut cx);
19141    let navigated = cx
19142        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19143        .await
19144        .expect("Failed to navigate to lookup references");
19145    assert_eq!(
19146        navigated,
19147        Navigated::Yes,
19148        "Should have navigated to references as a fallback after empty GoToDefinition response"
19149    );
19150    // We should not change the selections in the existing file,
19151    // if opening another milti buffer with the references
19152    cx.assert_editor_state(
19153        &r#"fn one() {
19154            let mut a = two();
19155        }
19156
19157        fn «twoˇ»() {}"#
19158            .unindent(),
19159    );
19160    let editors = cx.update_workspace(|workspace, _, cx| {
19161        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19162    });
19163    cx.update_editor(|_, _, test_editor_cx| {
19164        assert_eq!(
19165            editors.len(),
19166            2,
19167            "After falling back to references search, we open a new editor with the results"
19168        );
19169        let references_fallback_text = editors
19170            .into_iter()
19171            .find(|new_editor| *new_editor != test_editor_cx.entity())
19172            .expect("Should have one non-test editor now")
19173            .read(test_editor_cx)
19174            .text(test_editor_cx);
19175        assert_eq!(
19176            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19177            "Should use the range from the references response and not the GoToDefinition one"
19178        );
19179    });
19180}
19181
19182#[gpui::test]
19183async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19184    init_test(cx, |_| {});
19185    cx.update(|cx| {
19186        let mut editor_settings = EditorSettings::get_global(cx).clone();
19187        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19188        EditorSettings::override_global(editor_settings, cx);
19189    });
19190    let mut cx = EditorLspTestContext::new_rust(
19191        lsp::ServerCapabilities {
19192            definition_provider: Some(lsp::OneOf::Left(true)),
19193            references_provider: Some(lsp::OneOf::Left(true)),
19194            ..lsp::ServerCapabilities::default()
19195        },
19196        cx,
19197    )
19198    .await;
19199    let original_state = r#"fn one() {
19200        let mut a = ˇtwo();
19201    }
19202
19203    fn two() {}"#
19204        .unindent();
19205    cx.set_state(&original_state);
19206
19207    let mut go_to_definition = cx
19208        .lsp
19209        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19210            move |_, _| async move { Ok(None) },
19211        );
19212    let _references = cx
19213        .lsp
19214        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19215            panic!("Should not call for references with no go to definition fallback")
19216        });
19217
19218    let navigated = cx
19219        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19220        .await
19221        .expect("Failed to navigate to lookup references");
19222    go_to_definition
19223        .next()
19224        .await
19225        .expect("Should have called the go_to_definition handler");
19226
19227    assert_eq!(
19228        navigated,
19229        Navigated::No,
19230        "Should have navigated to references as a fallback after empty GoToDefinition response"
19231    );
19232    cx.assert_editor_state(&original_state);
19233    let editors = cx.update_workspace(|workspace, _, cx| {
19234        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19235    });
19236    cx.update_editor(|_, _, _| {
19237        assert_eq!(
19238            editors.len(),
19239            1,
19240            "After unsuccessful fallback, no other editor should have been opened"
19241        );
19242    });
19243}
19244
19245#[gpui::test]
19246async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19247    init_test(cx, |_| {});
19248
19249    let language = Arc::new(Language::new(
19250        LanguageConfig::default(),
19251        Some(tree_sitter_rust::LANGUAGE.into()),
19252    ));
19253
19254    let text = r#"
19255        #[cfg(test)]
19256        mod tests() {
19257            #[test]
19258            fn runnable_1() {
19259                let a = 1;
19260            }
19261
19262            #[test]
19263            fn runnable_2() {
19264                let a = 1;
19265                let b = 2;
19266            }
19267        }
19268    "#
19269    .unindent();
19270
19271    let fs = FakeFs::new(cx.executor());
19272    fs.insert_file("/file.rs", Default::default()).await;
19273
19274    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19275    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19276    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19277    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19278    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19279
19280    let editor = cx.new_window_entity(|window, cx| {
19281        Editor::new(
19282            EditorMode::full(),
19283            multi_buffer,
19284            Some(project.clone()),
19285            window,
19286            cx,
19287        )
19288    });
19289
19290    editor.update_in(cx, |editor, window, cx| {
19291        let snapshot = editor.buffer().read(cx).snapshot(cx);
19292        editor.tasks.insert(
19293            (buffer.read(cx).remote_id(), 3),
19294            RunnableTasks {
19295                templates: vec![],
19296                offset: snapshot.anchor_before(43),
19297                column: 0,
19298                extra_variables: HashMap::default(),
19299                context_range: BufferOffset(43)..BufferOffset(85),
19300            },
19301        );
19302        editor.tasks.insert(
19303            (buffer.read(cx).remote_id(), 8),
19304            RunnableTasks {
19305                templates: vec![],
19306                offset: snapshot.anchor_before(86),
19307                column: 0,
19308                extra_variables: HashMap::default(),
19309                context_range: BufferOffset(86)..BufferOffset(191),
19310            },
19311        );
19312
19313        // Test finding task when cursor is inside function body
19314        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19315            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19316        });
19317        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19318        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19319
19320        // Test finding task when cursor is on function name
19321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19322            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19323        });
19324        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19325        assert_eq!(row, 8, "Should find task when cursor is on function name");
19326    });
19327}
19328
19329#[gpui::test]
19330async fn test_folding_buffers(cx: &mut TestAppContext) {
19331    init_test(cx, |_| {});
19332
19333    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19334    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19335    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19336
19337    let fs = FakeFs::new(cx.executor());
19338    fs.insert_tree(
19339        path!("/a"),
19340        json!({
19341            "first.rs": sample_text_1,
19342            "second.rs": sample_text_2,
19343            "third.rs": sample_text_3,
19344        }),
19345    )
19346    .await;
19347    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19348    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19349    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19350    let worktree = project.update(cx, |project, cx| {
19351        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19352        assert_eq!(worktrees.len(), 1);
19353        worktrees.pop().unwrap()
19354    });
19355    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19356
19357    let buffer_1 = project
19358        .update(cx, |project, cx| {
19359            project.open_buffer((worktree_id, "first.rs"), cx)
19360        })
19361        .await
19362        .unwrap();
19363    let buffer_2 = project
19364        .update(cx, |project, cx| {
19365            project.open_buffer((worktree_id, "second.rs"), cx)
19366        })
19367        .await
19368        .unwrap();
19369    let buffer_3 = project
19370        .update(cx, |project, cx| {
19371            project.open_buffer((worktree_id, "third.rs"), cx)
19372        })
19373        .await
19374        .unwrap();
19375
19376    let multi_buffer = cx.new(|cx| {
19377        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19378        multi_buffer.push_excerpts(
19379            buffer_1.clone(),
19380            [
19381                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19382                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19383                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19384            ],
19385            cx,
19386        );
19387        multi_buffer.push_excerpts(
19388            buffer_2.clone(),
19389            [
19390                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19391                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19392                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19393            ],
19394            cx,
19395        );
19396        multi_buffer.push_excerpts(
19397            buffer_3.clone(),
19398            [
19399                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19400                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19401                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19402            ],
19403            cx,
19404        );
19405        multi_buffer
19406    });
19407    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19408        Editor::new(
19409            EditorMode::full(),
19410            multi_buffer.clone(),
19411            Some(project.clone()),
19412            window,
19413            cx,
19414        )
19415    });
19416
19417    assert_eq!(
19418        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19419        "\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",
19420    );
19421
19422    multi_buffer_editor.update(cx, |editor, cx| {
19423        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19424    });
19425    assert_eq!(
19426        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19427        "\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",
19428        "After folding the first buffer, its text should not be displayed"
19429    );
19430
19431    multi_buffer_editor.update(cx, |editor, cx| {
19432        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19433    });
19434    assert_eq!(
19435        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19436        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19437        "After folding the second buffer, its text should not be displayed"
19438    );
19439
19440    multi_buffer_editor.update(cx, |editor, cx| {
19441        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19442    });
19443    assert_eq!(
19444        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19445        "\n\n\n\n\n",
19446        "After folding the third buffer, its text should not be displayed"
19447    );
19448
19449    // Emulate selection inside the fold logic, that should work
19450    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19451        editor
19452            .snapshot(window, cx)
19453            .next_line_boundary(Point::new(0, 4));
19454    });
19455
19456    multi_buffer_editor.update(cx, |editor, cx| {
19457        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19458    });
19459    assert_eq!(
19460        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19461        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19462        "After unfolding the second buffer, its text should be displayed"
19463    );
19464
19465    // Typing inside of buffer 1 causes that buffer to be unfolded.
19466    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19467        assert_eq!(
19468            multi_buffer
19469                .read(cx)
19470                .snapshot(cx)
19471                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19472                .collect::<String>(),
19473            "bbbb"
19474        );
19475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19476            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19477        });
19478        editor.handle_input("B", window, cx);
19479    });
19480
19481    assert_eq!(
19482        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19483        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19484        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19485    );
19486
19487    multi_buffer_editor.update(cx, |editor, cx| {
19488        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19489    });
19490    assert_eq!(
19491        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19492        "\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",
19493        "After unfolding the all buffers, all original text should be displayed"
19494    );
19495}
19496
19497#[gpui::test]
19498async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19499    init_test(cx, |_| {});
19500
19501    let sample_text_1 = "1111\n2222\n3333".to_string();
19502    let sample_text_2 = "4444\n5555\n6666".to_string();
19503    let sample_text_3 = "7777\n8888\n9999".to_string();
19504
19505    let fs = FakeFs::new(cx.executor());
19506    fs.insert_tree(
19507        path!("/a"),
19508        json!({
19509            "first.rs": sample_text_1,
19510            "second.rs": sample_text_2,
19511            "third.rs": sample_text_3,
19512        }),
19513    )
19514    .await;
19515    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19516    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19517    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19518    let worktree = project.update(cx, |project, cx| {
19519        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19520        assert_eq!(worktrees.len(), 1);
19521        worktrees.pop().unwrap()
19522    });
19523    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19524
19525    let buffer_1 = project
19526        .update(cx, |project, cx| {
19527            project.open_buffer((worktree_id, "first.rs"), cx)
19528        })
19529        .await
19530        .unwrap();
19531    let buffer_2 = project
19532        .update(cx, |project, cx| {
19533            project.open_buffer((worktree_id, "second.rs"), cx)
19534        })
19535        .await
19536        .unwrap();
19537    let buffer_3 = project
19538        .update(cx, |project, cx| {
19539            project.open_buffer((worktree_id, "third.rs"), cx)
19540        })
19541        .await
19542        .unwrap();
19543
19544    let multi_buffer = cx.new(|cx| {
19545        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19546        multi_buffer.push_excerpts(
19547            buffer_1.clone(),
19548            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19549            cx,
19550        );
19551        multi_buffer.push_excerpts(
19552            buffer_2.clone(),
19553            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19554            cx,
19555        );
19556        multi_buffer.push_excerpts(
19557            buffer_3.clone(),
19558            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19559            cx,
19560        );
19561        multi_buffer
19562    });
19563
19564    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19565        Editor::new(
19566            EditorMode::full(),
19567            multi_buffer,
19568            Some(project.clone()),
19569            window,
19570            cx,
19571        )
19572    });
19573
19574    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19575    assert_eq!(
19576        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19577        full_text,
19578    );
19579
19580    multi_buffer_editor.update(cx, |editor, cx| {
19581        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19582    });
19583    assert_eq!(
19584        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19585        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19586        "After folding the first buffer, its text should not be displayed"
19587    );
19588
19589    multi_buffer_editor.update(cx, |editor, cx| {
19590        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19591    });
19592
19593    assert_eq!(
19594        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19595        "\n\n\n\n\n\n7777\n8888\n9999",
19596        "After folding the second buffer, its text should not be displayed"
19597    );
19598
19599    multi_buffer_editor.update(cx, |editor, cx| {
19600        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19601    });
19602    assert_eq!(
19603        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19604        "\n\n\n\n\n",
19605        "After folding the third buffer, its text should not be displayed"
19606    );
19607
19608    multi_buffer_editor.update(cx, |editor, cx| {
19609        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19610    });
19611    assert_eq!(
19612        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19613        "\n\n\n\n4444\n5555\n6666\n\n",
19614        "After unfolding the second buffer, its text should be displayed"
19615    );
19616
19617    multi_buffer_editor.update(cx, |editor, cx| {
19618        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19619    });
19620    assert_eq!(
19621        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19622        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19623        "After unfolding the first buffer, its text should be displayed"
19624    );
19625
19626    multi_buffer_editor.update(cx, |editor, cx| {
19627        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19628    });
19629    assert_eq!(
19630        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19631        full_text,
19632        "After unfolding all buffers, all original text should be displayed"
19633    );
19634}
19635
19636#[gpui::test]
19637async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19638    init_test(cx, |_| {});
19639
19640    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19641
19642    let fs = FakeFs::new(cx.executor());
19643    fs.insert_tree(
19644        path!("/a"),
19645        json!({
19646            "main.rs": sample_text,
19647        }),
19648    )
19649    .await;
19650    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19651    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19652    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19653    let worktree = project.update(cx, |project, cx| {
19654        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19655        assert_eq!(worktrees.len(), 1);
19656        worktrees.pop().unwrap()
19657    });
19658    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19659
19660    let buffer_1 = project
19661        .update(cx, |project, cx| {
19662            project.open_buffer((worktree_id, "main.rs"), cx)
19663        })
19664        .await
19665        .unwrap();
19666
19667    let multi_buffer = cx.new(|cx| {
19668        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19669        multi_buffer.push_excerpts(
19670            buffer_1.clone(),
19671            [ExcerptRange::new(
19672                Point::new(0, 0)
19673                    ..Point::new(
19674                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19675                        0,
19676                    ),
19677            )],
19678            cx,
19679        );
19680        multi_buffer
19681    });
19682    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19683        Editor::new(
19684            EditorMode::full(),
19685            multi_buffer,
19686            Some(project.clone()),
19687            window,
19688            cx,
19689        )
19690    });
19691
19692    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19693    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19694        enum TestHighlight {}
19695        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19696        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19697        editor.highlight_text::<TestHighlight>(
19698            vec![highlight_range.clone()],
19699            HighlightStyle::color(Hsla::green()),
19700            cx,
19701        );
19702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19703            s.select_ranges(Some(highlight_range))
19704        });
19705    });
19706
19707    let full_text = format!("\n\n{sample_text}");
19708    assert_eq!(
19709        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19710        full_text,
19711    );
19712}
19713
19714#[gpui::test]
19715async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19716    init_test(cx, |_| {});
19717    cx.update(|cx| {
19718        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19719            "keymaps/default-linux.json",
19720            cx,
19721        )
19722        .unwrap();
19723        cx.bind_keys(default_key_bindings);
19724    });
19725
19726    let (editor, cx) = cx.add_window_view(|window, cx| {
19727        let multi_buffer = MultiBuffer::build_multi(
19728            [
19729                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19730                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19731                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19732                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19733            ],
19734            cx,
19735        );
19736        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19737
19738        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19739        // fold all but the second buffer, so that we test navigating between two
19740        // adjacent folded buffers, as well as folded buffers at the start and
19741        // end the multibuffer
19742        editor.fold_buffer(buffer_ids[0], cx);
19743        editor.fold_buffer(buffer_ids[2], cx);
19744        editor.fold_buffer(buffer_ids[3], cx);
19745
19746        editor
19747    });
19748    cx.simulate_resize(size(px(1000.), px(1000.)));
19749
19750    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19751    cx.assert_excerpts_with_selections(indoc! {"
19752        [EXCERPT]
19753        ˇ[FOLDED]
19754        [EXCERPT]
19755        a1
19756        b1
19757        [EXCERPT]
19758        [FOLDED]
19759        [EXCERPT]
19760        [FOLDED]
19761        "
19762    });
19763    cx.simulate_keystroke("down");
19764    cx.assert_excerpts_with_selections(indoc! {"
19765        [EXCERPT]
19766        [FOLDED]
19767        [EXCERPT]
19768        ˇa1
19769        b1
19770        [EXCERPT]
19771        [FOLDED]
19772        [EXCERPT]
19773        [FOLDED]
19774        "
19775    });
19776    cx.simulate_keystroke("down");
19777    cx.assert_excerpts_with_selections(indoc! {"
19778        [EXCERPT]
19779        [FOLDED]
19780        [EXCERPT]
19781        a1
19782        ˇb1
19783        [EXCERPT]
19784        [FOLDED]
19785        [EXCERPT]
19786        [FOLDED]
19787        "
19788    });
19789    cx.simulate_keystroke("down");
19790    cx.assert_excerpts_with_selections(indoc! {"
19791        [EXCERPT]
19792        [FOLDED]
19793        [EXCERPT]
19794        a1
19795        b1
19796        ˇ[EXCERPT]
19797        [FOLDED]
19798        [EXCERPT]
19799        [FOLDED]
19800        "
19801    });
19802    cx.simulate_keystroke("down");
19803    cx.assert_excerpts_with_selections(indoc! {"
19804        [EXCERPT]
19805        [FOLDED]
19806        [EXCERPT]
19807        a1
19808        b1
19809        [EXCERPT]
19810        ˇ[FOLDED]
19811        [EXCERPT]
19812        [FOLDED]
19813        "
19814    });
19815    for _ in 0..5 {
19816        cx.simulate_keystroke("down");
19817        cx.assert_excerpts_with_selections(indoc! {"
19818            [EXCERPT]
19819            [FOLDED]
19820            [EXCERPT]
19821            a1
19822            b1
19823            [EXCERPT]
19824            [FOLDED]
19825            [EXCERPT]
19826            ˇ[FOLDED]
19827            "
19828        });
19829    }
19830
19831    cx.simulate_keystroke("up");
19832    cx.assert_excerpts_with_selections(indoc! {"
19833        [EXCERPT]
19834        [FOLDED]
19835        [EXCERPT]
19836        a1
19837        b1
19838        [EXCERPT]
19839        ˇ[FOLDED]
19840        [EXCERPT]
19841        [FOLDED]
19842        "
19843    });
19844    cx.simulate_keystroke("up");
19845    cx.assert_excerpts_with_selections(indoc! {"
19846        [EXCERPT]
19847        [FOLDED]
19848        [EXCERPT]
19849        a1
19850        b1
19851        ˇ[EXCERPT]
19852        [FOLDED]
19853        [EXCERPT]
19854        [FOLDED]
19855        "
19856    });
19857    cx.simulate_keystroke("up");
19858    cx.assert_excerpts_with_selections(indoc! {"
19859        [EXCERPT]
19860        [FOLDED]
19861        [EXCERPT]
19862        a1
19863        ˇb1
19864        [EXCERPT]
19865        [FOLDED]
19866        [EXCERPT]
19867        [FOLDED]
19868        "
19869    });
19870    cx.simulate_keystroke("up");
19871    cx.assert_excerpts_with_selections(indoc! {"
19872        [EXCERPT]
19873        [FOLDED]
19874        [EXCERPT]
19875        ˇa1
19876        b1
19877        [EXCERPT]
19878        [FOLDED]
19879        [EXCERPT]
19880        [FOLDED]
19881        "
19882    });
19883    for _ in 0..5 {
19884        cx.simulate_keystroke("up");
19885        cx.assert_excerpts_with_selections(indoc! {"
19886            [EXCERPT]
19887            ˇ[FOLDED]
19888            [EXCERPT]
19889            a1
19890            b1
19891            [EXCERPT]
19892            [FOLDED]
19893            [EXCERPT]
19894            [FOLDED]
19895            "
19896        });
19897    }
19898}
19899
19900#[gpui::test]
19901async fn test_inline_completion_text(cx: &mut TestAppContext) {
19902    init_test(cx, |_| {});
19903
19904    // Simple insertion
19905    assert_highlighted_edits(
19906        "Hello, world!",
19907        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19908        true,
19909        cx,
19910        |highlighted_edits, cx| {
19911            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19912            assert_eq!(highlighted_edits.highlights.len(), 1);
19913            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19914            assert_eq!(
19915                highlighted_edits.highlights[0].1.background_color,
19916                Some(cx.theme().status().created_background)
19917            );
19918        },
19919    )
19920    .await;
19921
19922    // Replacement
19923    assert_highlighted_edits(
19924        "This is a test.",
19925        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19926        false,
19927        cx,
19928        |highlighted_edits, cx| {
19929            assert_eq!(highlighted_edits.text, "That is a test.");
19930            assert_eq!(highlighted_edits.highlights.len(), 1);
19931            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19932            assert_eq!(
19933                highlighted_edits.highlights[0].1.background_color,
19934                Some(cx.theme().status().created_background)
19935            );
19936        },
19937    )
19938    .await;
19939
19940    // Multiple edits
19941    assert_highlighted_edits(
19942        "Hello, world!",
19943        vec![
19944            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19945            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19946        ],
19947        false,
19948        cx,
19949        |highlighted_edits, cx| {
19950            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19951            assert_eq!(highlighted_edits.highlights.len(), 2);
19952            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19953            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19954            assert_eq!(
19955                highlighted_edits.highlights[0].1.background_color,
19956                Some(cx.theme().status().created_background)
19957            );
19958            assert_eq!(
19959                highlighted_edits.highlights[1].1.background_color,
19960                Some(cx.theme().status().created_background)
19961            );
19962        },
19963    )
19964    .await;
19965
19966    // Multiple lines with edits
19967    assert_highlighted_edits(
19968        "First line\nSecond line\nThird line\nFourth line",
19969        vec![
19970            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19971            (
19972                Point::new(2, 0)..Point::new(2, 10),
19973                "New third line".to_string(),
19974            ),
19975            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19976        ],
19977        false,
19978        cx,
19979        |highlighted_edits, cx| {
19980            assert_eq!(
19981                highlighted_edits.text,
19982                "Second modified\nNew third line\nFourth updated line"
19983            );
19984            assert_eq!(highlighted_edits.highlights.len(), 3);
19985            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19986            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19987            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19988            for highlight in &highlighted_edits.highlights {
19989                assert_eq!(
19990                    highlight.1.background_color,
19991                    Some(cx.theme().status().created_background)
19992                );
19993            }
19994        },
19995    )
19996    .await;
19997}
19998
19999#[gpui::test]
20000async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20001    init_test(cx, |_| {});
20002
20003    // Deletion
20004    assert_highlighted_edits(
20005        "Hello, world!",
20006        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20007        true,
20008        cx,
20009        |highlighted_edits, cx| {
20010            assert_eq!(highlighted_edits.text, "Hello, world!");
20011            assert_eq!(highlighted_edits.highlights.len(), 1);
20012            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20013            assert_eq!(
20014                highlighted_edits.highlights[0].1.background_color,
20015                Some(cx.theme().status().deleted_background)
20016            );
20017        },
20018    )
20019    .await;
20020
20021    // Insertion
20022    assert_highlighted_edits(
20023        "Hello, world!",
20024        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20025        true,
20026        cx,
20027        |highlighted_edits, cx| {
20028            assert_eq!(highlighted_edits.highlights.len(), 1);
20029            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20030            assert_eq!(
20031                highlighted_edits.highlights[0].1.background_color,
20032                Some(cx.theme().status().created_background)
20033            );
20034        },
20035    )
20036    .await;
20037}
20038
20039async fn assert_highlighted_edits(
20040    text: &str,
20041    edits: Vec<(Range<Point>, String)>,
20042    include_deletions: bool,
20043    cx: &mut TestAppContext,
20044    assertion_fn: impl Fn(HighlightedText, &App),
20045) {
20046    let window = cx.add_window(|window, cx| {
20047        let buffer = MultiBuffer::build_simple(text, cx);
20048        Editor::new(EditorMode::full(), buffer, None, window, cx)
20049    });
20050    let cx = &mut VisualTestContext::from_window(*window, cx);
20051
20052    let (buffer, snapshot) = window
20053        .update(cx, |editor, _window, cx| {
20054            (
20055                editor.buffer().clone(),
20056                editor.buffer().read(cx).snapshot(cx),
20057            )
20058        })
20059        .unwrap();
20060
20061    let edits = edits
20062        .into_iter()
20063        .map(|(range, edit)| {
20064            (
20065                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20066                edit,
20067            )
20068        })
20069        .collect::<Vec<_>>();
20070
20071    let text_anchor_edits = edits
20072        .clone()
20073        .into_iter()
20074        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20075        .collect::<Vec<_>>();
20076
20077    let edit_preview = window
20078        .update(cx, |_, _window, cx| {
20079            buffer
20080                .read(cx)
20081                .as_singleton()
20082                .unwrap()
20083                .read(cx)
20084                .preview_edits(text_anchor_edits.into(), cx)
20085        })
20086        .unwrap()
20087        .await;
20088
20089    cx.update(|_window, cx| {
20090        let highlighted_edits = inline_completion_edit_text(
20091            &snapshot.as_singleton().unwrap().2,
20092            &edits,
20093            &edit_preview,
20094            include_deletions,
20095            cx,
20096        );
20097        assertion_fn(highlighted_edits, cx)
20098    });
20099}
20100
20101#[track_caller]
20102fn assert_breakpoint(
20103    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20104    path: &Arc<Path>,
20105    expected: Vec<(u32, Breakpoint)>,
20106) {
20107    if expected.len() == 0usize {
20108        assert!(!breakpoints.contains_key(path), "{}", path.display());
20109    } else {
20110        let mut breakpoint = breakpoints
20111            .get(path)
20112            .unwrap()
20113            .into_iter()
20114            .map(|breakpoint| {
20115                (
20116                    breakpoint.row,
20117                    Breakpoint {
20118                        message: breakpoint.message.clone(),
20119                        state: breakpoint.state,
20120                        condition: breakpoint.condition.clone(),
20121                        hit_condition: breakpoint.hit_condition.clone(),
20122                    },
20123                )
20124            })
20125            .collect::<Vec<_>>();
20126
20127        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20128
20129        assert_eq!(expected, breakpoint);
20130    }
20131}
20132
20133fn add_log_breakpoint_at_cursor(
20134    editor: &mut Editor,
20135    log_message: &str,
20136    window: &mut Window,
20137    cx: &mut Context<Editor>,
20138) {
20139    let (anchor, bp) = editor
20140        .breakpoints_at_cursors(window, cx)
20141        .first()
20142        .and_then(|(anchor, bp)| {
20143            if let Some(bp) = bp {
20144                Some((*anchor, bp.clone()))
20145            } else {
20146                None
20147            }
20148        })
20149        .unwrap_or_else(|| {
20150            let cursor_position: Point = editor.selections.newest(cx).head();
20151
20152            let breakpoint_position = editor
20153                .snapshot(window, cx)
20154                .display_snapshot
20155                .buffer_snapshot
20156                .anchor_before(Point::new(cursor_position.row, 0));
20157
20158            (breakpoint_position, Breakpoint::new_log(&log_message))
20159        });
20160
20161    editor.edit_breakpoint_at_anchor(
20162        anchor,
20163        bp,
20164        BreakpointEditAction::EditLogMessage(log_message.into()),
20165        cx,
20166    );
20167}
20168
20169#[gpui::test]
20170async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20171    init_test(cx, |_| {});
20172
20173    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20174    let fs = FakeFs::new(cx.executor());
20175    fs.insert_tree(
20176        path!("/a"),
20177        json!({
20178            "main.rs": sample_text,
20179        }),
20180    )
20181    .await;
20182    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20183    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20184    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20185
20186    let fs = FakeFs::new(cx.executor());
20187    fs.insert_tree(
20188        path!("/a"),
20189        json!({
20190            "main.rs": sample_text,
20191        }),
20192    )
20193    .await;
20194    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20195    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20196    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20197    let worktree_id = workspace
20198        .update(cx, |workspace, _window, cx| {
20199            workspace.project().update(cx, |project, cx| {
20200                project.worktrees(cx).next().unwrap().read(cx).id()
20201            })
20202        })
20203        .unwrap();
20204
20205    let buffer = project
20206        .update(cx, |project, cx| {
20207            project.open_buffer((worktree_id, "main.rs"), cx)
20208        })
20209        .await
20210        .unwrap();
20211
20212    let (editor, cx) = cx.add_window_view(|window, cx| {
20213        Editor::new(
20214            EditorMode::full(),
20215            MultiBuffer::build_from_buffer(buffer, cx),
20216            Some(project.clone()),
20217            window,
20218            cx,
20219        )
20220    });
20221
20222    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20223    let abs_path = project.read_with(cx, |project, cx| {
20224        project
20225            .absolute_path(&project_path, cx)
20226            .map(|path_buf| Arc::from(path_buf.to_owned()))
20227            .unwrap()
20228    });
20229
20230    // assert we can add breakpoint on the first line
20231    editor.update_in(cx, |editor, window, cx| {
20232        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20233        editor.move_to_end(&MoveToEnd, window, cx);
20234        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20235    });
20236
20237    let breakpoints = editor.update(cx, |editor, cx| {
20238        editor
20239            .breakpoint_store()
20240            .as_ref()
20241            .unwrap()
20242            .read(cx)
20243            .all_source_breakpoints(cx)
20244            .clone()
20245    });
20246
20247    assert_eq!(1, breakpoints.len());
20248    assert_breakpoint(
20249        &breakpoints,
20250        &abs_path,
20251        vec![
20252            (0, Breakpoint::new_standard()),
20253            (3, Breakpoint::new_standard()),
20254        ],
20255    );
20256
20257    editor.update_in(cx, |editor, window, cx| {
20258        editor.move_to_beginning(&MoveToBeginning, window, cx);
20259        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20260    });
20261
20262    let breakpoints = editor.update(cx, |editor, cx| {
20263        editor
20264            .breakpoint_store()
20265            .as_ref()
20266            .unwrap()
20267            .read(cx)
20268            .all_source_breakpoints(cx)
20269            .clone()
20270    });
20271
20272    assert_eq!(1, breakpoints.len());
20273    assert_breakpoint(
20274        &breakpoints,
20275        &abs_path,
20276        vec![(3, Breakpoint::new_standard())],
20277    );
20278
20279    editor.update_in(cx, |editor, window, cx| {
20280        editor.move_to_end(&MoveToEnd, window, cx);
20281        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20282    });
20283
20284    let breakpoints = editor.update(cx, |editor, cx| {
20285        editor
20286            .breakpoint_store()
20287            .as_ref()
20288            .unwrap()
20289            .read(cx)
20290            .all_source_breakpoints(cx)
20291            .clone()
20292    });
20293
20294    assert_eq!(0, breakpoints.len());
20295    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20296}
20297
20298#[gpui::test]
20299async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20300    init_test(cx, |_| {});
20301
20302    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20303
20304    let fs = FakeFs::new(cx.executor());
20305    fs.insert_tree(
20306        path!("/a"),
20307        json!({
20308            "main.rs": sample_text,
20309        }),
20310    )
20311    .await;
20312    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20313    let (workspace, cx) =
20314        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20315
20316    let worktree_id = workspace.update(cx, |workspace, cx| {
20317        workspace.project().update(cx, |project, cx| {
20318            project.worktrees(cx).next().unwrap().read(cx).id()
20319        })
20320    });
20321
20322    let buffer = project
20323        .update(cx, |project, cx| {
20324            project.open_buffer((worktree_id, "main.rs"), cx)
20325        })
20326        .await
20327        .unwrap();
20328
20329    let (editor, cx) = cx.add_window_view(|window, cx| {
20330        Editor::new(
20331            EditorMode::full(),
20332            MultiBuffer::build_from_buffer(buffer, cx),
20333            Some(project.clone()),
20334            window,
20335            cx,
20336        )
20337    });
20338
20339    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20340    let abs_path = project.read_with(cx, |project, cx| {
20341        project
20342            .absolute_path(&project_path, cx)
20343            .map(|path_buf| Arc::from(path_buf.to_owned()))
20344            .unwrap()
20345    });
20346
20347    editor.update_in(cx, |editor, window, cx| {
20348        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20349    });
20350
20351    let breakpoints = editor.update(cx, |editor, cx| {
20352        editor
20353            .breakpoint_store()
20354            .as_ref()
20355            .unwrap()
20356            .read(cx)
20357            .all_source_breakpoints(cx)
20358            .clone()
20359    });
20360
20361    assert_breakpoint(
20362        &breakpoints,
20363        &abs_path,
20364        vec![(0, Breakpoint::new_log("hello world"))],
20365    );
20366
20367    // Removing a log message from a log breakpoint should remove it
20368    editor.update_in(cx, |editor, window, cx| {
20369        add_log_breakpoint_at_cursor(editor, "", window, cx);
20370    });
20371
20372    let breakpoints = editor.update(cx, |editor, cx| {
20373        editor
20374            .breakpoint_store()
20375            .as_ref()
20376            .unwrap()
20377            .read(cx)
20378            .all_source_breakpoints(cx)
20379            .clone()
20380    });
20381
20382    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20383
20384    editor.update_in(cx, |editor, window, cx| {
20385        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20386        editor.move_to_end(&MoveToEnd, window, cx);
20387        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20388        // Not adding a log message to a standard breakpoint shouldn't remove it
20389        add_log_breakpoint_at_cursor(editor, "", window, cx);
20390    });
20391
20392    let breakpoints = editor.update(cx, |editor, cx| {
20393        editor
20394            .breakpoint_store()
20395            .as_ref()
20396            .unwrap()
20397            .read(cx)
20398            .all_source_breakpoints(cx)
20399            .clone()
20400    });
20401
20402    assert_breakpoint(
20403        &breakpoints,
20404        &abs_path,
20405        vec![
20406            (0, Breakpoint::new_standard()),
20407            (3, Breakpoint::new_standard()),
20408        ],
20409    );
20410
20411    editor.update_in(cx, |editor, window, cx| {
20412        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20413    });
20414
20415    let breakpoints = editor.update(cx, |editor, cx| {
20416        editor
20417            .breakpoint_store()
20418            .as_ref()
20419            .unwrap()
20420            .read(cx)
20421            .all_source_breakpoints(cx)
20422            .clone()
20423    });
20424
20425    assert_breakpoint(
20426        &breakpoints,
20427        &abs_path,
20428        vec![
20429            (0, Breakpoint::new_standard()),
20430            (3, Breakpoint::new_log("hello world")),
20431        ],
20432    );
20433
20434    editor.update_in(cx, |editor, window, cx| {
20435        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20436    });
20437
20438    let breakpoints = editor.update(cx, |editor, cx| {
20439        editor
20440            .breakpoint_store()
20441            .as_ref()
20442            .unwrap()
20443            .read(cx)
20444            .all_source_breakpoints(cx)
20445            .clone()
20446    });
20447
20448    assert_breakpoint(
20449        &breakpoints,
20450        &abs_path,
20451        vec![
20452            (0, Breakpoint::new_standard()),
20453            (3, Breakpoint::new_log("hello Earth!!")),
20454        ],
20455    );
20456}
20457
20458/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20459/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20460/// or when breakpoints were placed out of order. This tests for a regression too
20461#[gpui::test]
20462async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20463    init_test(cx, |_| {});
20464
20465    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20466    let fs = FakeFs::new(cx.executor());
20467    fs.insert_tree(
20468        path!("/a"),
20469        json!({
20470            "main.rs": sample_text,
20471        }),
20472    )
20473    .await;
20474    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20475    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20476    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20477
20478    let fs = FakeFs::new(cx.executor());
20479    fs.insert_tree(
20480        path!("/a"),
20481        json!({
20482            "main.rs": sample_text,
20483        }),
20484    )
20485    .await;
20486    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20487    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20488    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20489    let worktree_id = workspace
20490        .update(cx, |workspace, _window, cx| {
20491            workspace.project().update(cx, |project, cx| {
20492                project.worktrees(cx).next().unwrap().read(cx).id()
20493            })
20494        })
20495        .unwrap();
20496
20497    let buffer = project
20498        .update(cx, |project, cx| {
20499            project.open_buffer((worktree_id, "main.rs"), cx)
20500        })
20501        .await
20502        .unwrap();
20503
20504    let (editor, cx) = cx.add_window_view(|window, cx| {
20505        Editor::new(
20506            EditorMode::full(),
20507            MultiBuffer::build_from_buffer(buffer, cx),
20508            Some(project.clone()),
20509            window,
20510            cx,
20511        )
20512    });
20513
20514    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20515    let abs_path = project.read_with(cx, |project, cx| {
20516        project
20517            .absolute_path(&project_path, cx)
20518            .map(|path_buf| Arc::from(path_buf.to_owned()))
20519            .unwrap()
20520    });
20521
20522    // assert we can add breakpoint on the first line
20523    editor.update_in(cx, |editor, window, cx| {
20524        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20525        editor.move_to_end(&MoveToEnd, window, cx);
20526        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20527        editor.move_up(&MoveUp, window, cx);
20528        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20529    });
20530
20531    let breakpoints = editor.update(cx, |editor, cx| {
20532        editor
20533            .breakpoint_store()
20534            .as_ref()
20535            .unwrap()
20536            .read(cx)
20537            .all_source_breakpoints(cx)
20538            .clone()
20539    });
20540
20541    assert_eq!(1, breakpoints.len());
20542    assert_breakpoint(
20543        &breakpoints,
20544        &abs_path,
20545        vec![
20546            (0, Breakpoint::new_standard()),
20547            (2, Breakpoint::new_standard()),
20548            (3, Breakpoint::new_standard()),
20549        ],
20550    );
20551
20552    editor.update_in(cx, |editor, window, cx| {
20553        editor.move_to_beginning(&MoveToBeginning, window, cx);
20554        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20555        editor.move_to_end(&MoveToEnd, window, cx);
20556        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20557        // Disabling a breakpoint that doesn't exist should do nothing
20558        editor.move_up(&MoveUp, window, cx);
20559        editor.move_up(&MoveUp, window, cx);
20560        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20561    });
20562
20563    let breakpoints = editor.update(cx, |editor, cx| {
20564        editor
20565            .breakpoint_store()
20566            .as_ref()
20567            .unwrap()
20568            .read(cx)
20569            .all_source_breakpoints(cx)
20570            .clone()
20571    });
20572
20573    let disable_breakpoint = {
20574        let mut bp = Breakpoint::new_standard();
20575        bp.state = BreakpointState::Disabled;
20576        bp
20577    };
20578
20579    assert_eq!(1, breakpoints.len());
20580    assert_breakpoint(
20581        &breakpoints,
20582        &abs_path,
20583        vec![
20584            (0, disable_breakpoint.clone()),
20585            (2, Breakpoint::new_standard()),
20586            (3, disable_breakpoint.clone()),
20587        ],
20588    );
20589
20590    editor.update_in(cx, |editor, window, cx| {
20591        editor.move_to_beginning(&MoveToBeginning, window, cx);
20592        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20593        editor.move_to_end(&MoveToEnd, window, cx);
20594        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20595        editor.move_up(&MoveUp, window, cx);
20596        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20597    });
20598
20599    let breakpoints = editor.update(cx, |editor, cx| {
20600        editor
20601            .breakpoint_store()
20602            .as_ref()
20603            .unwrap()
20604            .read(cx)
20605            .all_source_breakpoints(cx)
20606            .clone()
20607    });
20608
20609    assert_eq!(1, breakpoints.len());
20610    assert_breakpoint(
20611        &breakpoints,
20612        &abs_path,
20613        vec![
20614            (0, Breakpoint::new_standard()),
20615            (2, disable_breakpoint),
20616            (3, Breakpoint::new_standard()),
20617        ],
20618    );
20619}
20620
20621#[gpui::test]
20622async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20623    init_test(cx, |_| {});
20624    let capabilities = lsp::ServerCapabilities {
20625        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20626            prepare_provider: Some(true),
20627            work_done_progress_options: Default::default(),
20628        })),
20629        ..Default::default()
20630    };
20631    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20632
20633    cx.set_state(indoc! {"
20634        struct Fˇoo {}
20635    "});
20636
20637    cx.update_editor(|editor, _, cx| {
20638        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20639        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20640        editor.highlight_background::<DocumentHighlightRead>(
20641            &[highlight_range],
20642            |theme| theme.colors().editor_document_highlight_read_background,
20643            cx,
20644        );
20645    });
20646
20647    let mut prepare_rename_handler = cx
20648        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20649            move |_, _, _| async move {
20650                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20651                    start: lsp::Position {
20652                        line: 0,
20653                        character: 7,
20654                    },
20655                    end: lsp::Position {
20656                        line: 0,
20657                        character: 10,
20658                    },
20659                })))
20660            },
20661        );
20662    let prepare_rename_task = cx
20663        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20664        .expect("Prepare rename was not started");
20665    prepare_rename_handler.next().await.unwrap();
20666    prepare_rename_task.await.expect("Prepare rename failed");
20667
20668    let mut rename_handler =
20669        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20670            let edit = lsp::TextEdit {
20671                range: lsp::Range {
20672                    start: lsp::Position {
20673                        line: 0,
20674                        character: 7,
20675                    },
20676                    end: lsp::Position {
20677                        line: 0,
20678                        character: 10,
20679                    },
20680                },
20681                new_text: "FooRenamed".to_string(),
20682            };
20683            Ok(Some(lsp::WorkspaceEdit::new(
20684                // Specify the same edit twice
20685                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20686            )))
20687        });
20688    let rename_task = cx
20689        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20690        .expect("Confirm rename was not started");
20691    rename_handler.next().await.unwrap();
20692    rename_task.await.expect("Confirm rename failed");
20693    cx.run_until_parked();
20694
20695    // Despite two edits, only one is actually applied as those are identical
20696    cx.assert_editor_state(indoc! {"
20697        struct FooRenamedˇ {}
20698    "});
20699}
20700
20701#[gpui::test]
20702async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20703    init_test(cx, |_| {});
20704    // These capabilities indicate that the server does not support prepare rename.
20705    let capabilities = lsp::ServerCapabilities {
20706        rename_provider: Some(lsp::OneOf::Left(true)),
20707        ..Default::default()
20708    };
20709    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20710
20711    cx.set_state(indoc! {"
20712        struct Fˇoo {}
20713    "});
20714
20715    cx.update_editor(|editor, _window, cx| {
20716        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20717        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20718        editor.highlight_background::<DocumentHighlightRead>(
20719            &[highlight_range],
20720            |theme| theme.colors().editor_document_highlight_read_background,
20721            cx,
20722        );
20723    });
20724
20725    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20726        .expect("Prepare rename was not started")
20727        .await
20728        .expect("Prepare rename failed");
20729
20730    let mut rename_handler =
20731        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20732            let edit = lsp::TextEdit {
20733                range: lsp::Range {
20734                    start: lsp::Position {
20735                        line: 0,
20736                        character: 7,
20737                    },
20738                    end: lsp::Position {
20739                        line: 0,
20740                        character: 10,
20741                    },
20742                },
20743                new_text: "FooRenamed".to_string(),
20744            };
20745            Ok(Some(lsp::WorkspaceEdit::new(
20746                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20747            )))
20748        });
20749    let rename_task = cx
20750        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20751        .expect("Confirm rename was not started");
20752    rename_handler.next().await.unwrap();
20753    rename_task.await.expect("Confirm rename failed");
20754    cx.run_until_parked();
20755
20756    // Correct range is renamed, as `surrounding_word` is used to find it.
20757    cx.assert_editor_state(indoc! {"
20758        struct FooRenamedˇ {}
20759    "});
20760}
20761
20762#[gpui::test]
20763async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20764    init_test(cx, |_| {});
20765    let mut cx = EditorTestContext::new(cx).await;
20766
20767    let language = Arc::new(
20768        Language::new(
20769            LanguageConfig::default(),
20770            Some(tree_sitter_html::LANGUAGE.into()),
20771        )
20772        .with_brackets_query(
20773            r#"
20774            ("<" @open "/>" @close)
20775            ("</" @open ">" @close)
20776            ("<" @open ">" @close)
20777            ("\"" @open "\"" @close)
20778            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20779        "#,
20780        )
20781        .unwrap(),
20782    );
20783    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20784
20785    cx.set_state(indoc! {"
20786        <span>ˇ</span>
20787    "});
20788    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20789    cx.assert_editor_state(indoc! {"
20790        <span>
20791        ˇ
20792        </span>
20793    "});
20794
20795    cx.set_state(indoc! {"
20796        <span><span></span>ˇ</span>
20797    "});
20798    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20799    cx.assert_editor_state(indoc! {"
20800        <span><span></span>
20801        ˇ</span>
20802    "});
20803
20804    cx.set_state(indoc! {"
20805        <span>ˇ
20806        </span>
20807    "});
20808    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20809    cx.assert_editor_state(indoc! {"
20810        <span>
20811        ˇ
20812        </span>
20813    "});
20814}
20815
20816#[gpui::test(iterations = 10)]
20817async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20818    init_test(cx, |_| {});
20819
20820    let fs = FakeFs::new(cx.executor());
20821    fs.insert_tree(
20822        path!("/dir"),
20823        json!({
20824            "a.ts": "a",
20825        }),
20826    )
20827    .await;
20828
20829    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20830    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20831    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20832
20833    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20834    language_registry.add(Arc::new(Language::new(
20835        LanguageConfig {
20836            name: "TypeScript".into(),
20837            matcher: LanguageMatcher {
20838                path_suffixes: vec!["ts".to_string()],
20839                ..Default::default()
20840            },
20841            ..Default::default()
20842        },
20843        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20844    )));
20845    let mut fake_language_servers = language_registry.register_fake_lsp(
20846        "TypeScript",
20847        FakeLspAdapter {
20848            capabilities: lsp::ServerCapabilities {
20849                code_lens_provider: Some(lsp::CodeLensOptions {
20850                    resolve_provider: Some(true),
20851                }),
20852                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20853                    commands: vec!["_the/command".to_string()],
20854                    ..lsp::ExecuteCommandOptions::default()
20855                }),
20856                ..lsp::ServerCapabilities::default()
20857            },
20858            ..FakeLspAdapter::default()
20859        },
20860    );
20861
20862    let (buffer, _handle) = project
20863        .update(cx, |p, cx| {
20864            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20865        })
20866        .await
20867        .unwrap();
20868    cx.executor().run_until_parked();
20869
20870    let fake_server = fake_language_servers.next().await.unwrap();
20871
20872    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20873    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20874    drop(buffer_snapshot);
20875    let actions = cx
20876        .update_window(*workspace, |_, window, cx| {
20877            project.code_actions(&buffer, anchor..anchor, window, cx)
20878        })
20879        .unwrap();
20880
20881    fake_server
20882        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20883            Ok(Some(vec![
20884                lsp::CodeLens {
20885                    range: lsp::Range::default(),
20886                    command: Some(lsp::Command {
20887                        title: "Code lens command".to_owned(),
20888                        command: "_the/command".to_owned(),
20889                        arguments: None,
20890                    }),
20891                    data: None,
20892                },
20893                lsp::CodeLens {
20894                    range: lsp::Range::default(),
20895                    command: Some(lsp::Command {
20896                        title: "Command not in capabilities".to_owned(),
20897                        command: "not in capabilities".to_owned(),
20898                        arguments: None,
20899                    }),
20900                    data: None,
20901                },
20902                lsp::CodeLens {
20903                    range: lsp::Range {
20904                        start: lsp::Position {
20905                            line: 1,
20906                            character: 1,
20907                        },
20908                        end: lsp::Position {
20909                            line: 1,
20910                            character: 1,
20911                        },
20912                    },
20913                    command: Some(lsp::Command {
20914                        title: "Command not in range".to_owned(),
20915                        command: "_the/command".to_owned(),
20916                        arguments: None,
20917                    }),
20918                    data: None,
20919                },
20920            ]))
20921        })
20922        .next()
20923        .await;
20924
20925    let actions = actions.await.unwrap();
20926    assert_eq!(
20927        actions.len(),
20928        1,
20929        "Should have only one valid action for the 0..0 range"
20930    );
20931    let action = actions[0].clone();
20932    let apply = project.update(cx, |project, cx| {
20933        project.apply_code_action(buffer.clone(), action, true, cx)
20934    });
20935
20936    // Resolving the code action does not populate its edits. In absence of
20937    // edits, we must execute the given command.
20938    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20939        |mut lens, _| async move {
20940            let lens_command = lens.command.as_mut().expect("should have a command");
20941            assert_eq!(lens_command.title, "Code lens command");
20942            lens_command.arguments = Some(vec![json!("the-argument")]);
20943            Ok(lens)
20944        },
20945    );
20946
20947    // While executing the command, the language server sends the editor
20948    // a `workspaceEdit` request.
20949    fake_server
20950        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20951            let fake = fake_server.clone();
20952            move |params, _| {
20953                assert_eq!(params.command, "_the/command");
20954                let fake = fake.clone();
20955                async move {
20956                    fake.server
20957                        .request::<lsp::request::ApplyWorkspaceEdit>(
20958                            lsp::ApplyWorkspaceEditParams {
20959                                label: None,
20960                                edit: lsp::WorkspaceEdit {
20961                                    changes: Some(
20962                                        [(
20963                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20964                                            vec![lsp::TextEdit {
20965                                                range: lsp::Range::new(
20966                                                    lsp::Position::new(0, 0),
20967                                                    lsp::Position::new(0, 0),
20968                                                ),
20969                                                new_text: "X".into(),
20970                                            }],
20971                                        )]
20972                                        .into_iter()
20973                                        .collect(),
20974                                    ),
20975                                    ..Default::default()
20976                                },
20977                            },
20978                        )
20979                        .await
20980                        .into_response()
20981                        .unwrap();
20982                    Ok(Some(json!(null)))
20983                }
20984            }
20985        })
20986        .next()
20987        .await;
20988
20989    // Applying the code lens command returns a project transaction containing the edits
20990    // sent by the language server in its `workspaceEdit` request.
20991    let transaction = apply.await.unwrap();
20992    assert!(transaction.0.contains_key(&buffer));
20993    buffer.update(cx, |buffer, cx| {
20994        assert_eq!(buffer.text(), "Xa");
20995        buffer.undo(cx);
20996        assert_eq!(buffer.text(), "a");
20997    });
20998}
20999
21000#[gpui::test]
21001async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21002    init_test(cx, |_| {});
21003
21004    let fs = FakeFs::new(cx.executor());
21005    let main_text = r#"fn main() {
21006println!("1");
21007println!("2");
21008println!("3");
21009println!("4");
21010println!("5");
21011}"#;
21012    let lib_text = "mod foo {}";
21013    fs.insert_tree(
21014        path!("/a"),
21015        json!({
21016            "lib.rs": lib_text,
21017            "main.rs": main_text,
21018        }),
21019    )
21020    .await;
21021
21022    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21023    let (workspace, cx) =
21024        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21025    let worktree_id = workspace.update(cx, |workspace, cx| {
21026        workspace.project().update(cx, |project, cx| {
21027            project.worktrees(cx).next().unwrap().read(cx).id()
21028        })
21029    });
21030
21031    let expected_ranges = vec![
21032        Point::new(0, 0)..Point::new(0, 0),
21033        Point::new(1, 0)..Point::new(1, 1),
21034        Point::new(2, 0)..Point::new(2, 2),
21035        Point::new(3, 0)..Point::new(3, 3),
21036    ];
21037
21038    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21039    let editor_1 = workspace
21040        .update_in(cx, |workspace, window, cx| {
21041            workspace.open_path(
21042                (worktree_id, "main.rs"),
21043                Some(pane_1.downgrade()),
21044                true,
21045                window,
21046                cx,
21047            )
21048        })
21049        .unwrap()
21050        .await
21051        .downcast::<Editor>()
21052        .unwrap();
21053    pane_1.update(cx, |pane, cx| {
21054        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21055        open_editor.update(cx, |editor, cx| {
21056            assert_eq!(
21057                editor.display_text(cx),
21058                main_text,
21059                "Original main.rs text on initial open",
21060            );
21061            assert_eq!(
21062                editor
21063                    .selections
21064                    .all::<Point>(cx)
21065                    .into_iter()
21066                    .map(|s| s.range())
21067                    .collect::<Vec<_>>(),
21068                vec![Point::zero()..Point::zero()],
21069                "Default selections on initial open",
21070            );
21071        })
21072    });
21073    editor_1.update_in(cx, |editor, window, cx| {
21074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21075            s.select_ranges(expected_ranges.clone());
21076        });
21077    });
21078
21079    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21080        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21081    });
21082    let editor_2 = workspace
21083        .update_in(cx, |workspace, window, cx| {
21084            workspace.open_path(
21085                (worktree_id, "main.rs"),
21086                Some(pane_2.downgrade()),
21087                true,
21088                window,
21089                cx,
21090            )
21091        })
21092        .unwrap()
21093        .await
21094        .downcast::<Editor>()
21095        .unwrap();
21096    pane_2.update(cx, |pane, cx| {
21097        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21098        open_editor.update(cx, |editor, cx| {
21099            assert_eq!(
21100                editor.display_text(cx),
21101                main_text,
21102                "Original main.rs text on initial open in another panel",
21103            );
21104            assert_eq!(
21105                editor
21106                    .selections
21107                    .all::<Point>(cx)
21108                    .into_iter()
21109                    .map(|s| s.range())
21110                    .collect::<Vec<_>>(),
21111                vec![Point::zero()..Point::zero()],
21112                "Default selections on initial open in another panel",
21113            );
21114        })
21115    });
21116
21117    editor_2.update_in(cx, |editor, window, cx| {
21118        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21119    });
21120
21121    let _other_editor_1 = workspace
21122        .update_in(cx, |workspace, window, cx| {
21123            workspace.open_path(
21124                (worktree_id, "lib.rs"),
21125                Some(pane_1.downgrade()),
21126                true,
21127                window,
21128                cx,
21129            )
21130        })
21131        .unwrap()
21132        .await
21133        .downcast::<Editor>()
21134        .unwrap();
21135    pane_1
21136        .update_in(cx, |pane, window, cx| {
21137            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21138        })
21139        .await
21140        .unwrap();
21141    drop(editor_1);
21142    pane_1.update(cx, |pane, cx| {
21143        pane.active_item()
21144            .unwrap()
21145            .downcast::<Editor>()
21146            .unwrap()
21147            .update(cx, |editor, cx| {
21148                assert_eq!(
21149                    editor.display_text(cx),
21150                    lib_text,
21151                    "Other file should be open and active",
21152                );
21153            });
21154        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21155    });
21156
21157    let _other_editor_2 = workspace
21158        .update_in(cx, |workspace, window, cx| {
21159            workspace.open_path(
21160                (worktree_id, "lib.rs"),
21161                Some(pane_2.downgrade()),
21162                true,
21163                window,
21164                cx,
21165            )
21166        })
21167        .unwrap()
21168        .await
21169        .downcast::<Editor>()
21170        .unwrap();
21171    pane_2
21172        .update_in(cx, |pane, window, cx| {
21173            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21174        })
21175        .await
21176        .unwrap();
21177    drop(editor_2);
21178    pane_2.update(cx, |pane, cx| {
21179        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21180        open_editor.update(cx, |editor, cx| {
21181            assert_eq!(
21182                editor.display_text(cx),
21183                lib_text,
21184                "Other file should be open and active in another panel too",
21185            );
21186        });
21187        assert_eq!(
21188            pane.items().count(),
21189            1,
21190            "No other editors should be open in another pane",
21191        );
21192    });
21193
21194    let _editor_1_reopened = workspace
21195        .update_in(cx, |workspace, window, cx| {
21196            workspace.open_path(
21197                (worktree_id, "main.rs"),
21198                Some(pane_1.downgrade()),
21199                true,
21200                window,
21201                cx,
21202            )
21203        })
21204        .unwrap()
21205        .await
21206        .downcast::<Editor>()
21207        .unwrap();
21208    let _editor_2_reopened = workspace
21209        .update_in(cx, |workspace, window, cx| {
21210            workspace.open_path(
21211                (worktree_id, "main.rs"),
21212                Some(pane_2.downgrade()),
21213                true,
21214                window,
21215                cx,
21216            )
21217        })
21218        .unwrap()
21219        .await
21220        .downcast::<Editor>()
21221        .unwrap();
21222    pane_1.update(cx, |pane, cx| {
21223        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21224        open_editor.update(cx, |editor, cx| {
21225            assert_eq!(
21226                editor.display_text(cx),
21227                main_text,
21228                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21229            );
21230            assert_eq!(
21231                editor
21232                    .selections
21233                    .all::<Point>(cx)
21234                    .into_iter()
21235                    .map(|s| s.range())
21236                    .collect::<Vec<_>>(),
21237                expected_ranges,
21238                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21239            );
21240        })
21241    });
21242    pane_2.update(cx, |pane, cx| {
21243        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21244        open_editor.update(cx, |editor, cx| {
21245            assert_eq!(
21246                editor.display_text(cx),
21247                r#"fn main() {
21248⋯rintln!("1");
21249⋯intln!("2");
21250⋯ntln!("3");
21251println!("4");
21252println!("5");
21253}"#,
21254                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21255            );
21256            assert_eq!(
21257                editor
21258                    .selections
21259                    .all::<Point>(cx)
21260                    .into_iter()
21261                    .map(|s| s.range())
21262                    .collect::<Vec<_>>(),
21263                vec![Point::zero()..Point::zero()],
21264                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21265            );
21266        })
21267    });
21268}
21269
21270#[gpui::test]
21271async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21272    init_test(cx, |_| {});
21273
21274    let fs = FakeFs::new(cx.executor());
21275    let main_text = r#"fn main() {
21276println!("1");
21277println!("2");
21278println!("3");
21279println!("4");
21280println!("5");
21281}"#;
21282    let lib_text = "mod foo {}";
21283    fs.insert_tree(
21284        path!("/a"),
21285        json!({
21286            "lib.rs": lib_text,
21287            "main.rs": main_text,
21288        }),
21289    )
21290    .await;
21291
21292    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21293    let (workspace, cx) =
21294        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21295    let worktree_id = workspace.update(cx, |workspace, cx| {
21296        workspace.project().update(cx, |project, cx| {
21297            project.worktrees(cx).next().unwrap().read(cx).id()
21298        })
21299    });
21300
21301    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21302    let editor = workspace
21303        .update_in(cx, |workspace, window, cx| {
21304            workspace.open_path(
21305                (worktree_id, "main.rs"),
21306                Some(pane.downgrade()),
21307                true,
21308                window,
21309                cx,
21310            )
21311        })
21312        .unwrap()
21313        .await
21314        .downcast::<Editor>()
21315        .unwrap();
21316    pane.update(cx, |pane, cx| {
21317        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21318        open_editor.update(cx, |editor, cx| {
21319            assert_eq!(
21320                editor.display_text(cx),
21321                main_text,
21322                "Original main.rs text on initial open",
21323            );
21324        })
21325    });
21326    editor.update_in(cx, |editor, window, cx| {
21327        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21328    });
21329
21330    cx.update_global(|store: &mut SettingsStore, cx| {
21331        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21332            s.restore_on_file_reopen = Some(false);
21333        });
21334    });
21335    editor.update_in(cx, |editor, window, cx| {
21336        editor.fold_ranges(
21337            vec![
21338                Point::new(1, 0)..Point::new(1, 1),
21339                Point::new(2, 0)..Point::new(2, 2),
21340                Point::new(3, 0)..Point::new(3, 3),
21341            ],
21342            false,
21343            window,
21344            cx,
21345        );
21346    });
21347    pane.update_in(cx, |pane, window, cx| {
21348        pane.close_all_items(&CloseAllItems::default(), window, cx)
21349    })
21350    .await
21351    .unwrap();
21352    pane.update(cx, |pane, _| {
21353        assert!(pane.active_item().is_none());
21354    });
21355    cx.update_global(|store: &mut SettingsStore, cx| {
21356        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21357            s.restore_on_file_reopen = Some(true);
21358        });
21359    });
21360
21361    let _editor_reopened = workspace
21362        .update_in(cx, |workspace, window, cx| {
21363            workspace.open_path(
21364                (worktree_id, "main.rs"),
21365                Some(pane.downgrade()),
21366                true,
21367                window,
21368                cx,
21369            )
21370        })
21371        .unwrap()
21372        .await
21373        .downcast::<Editor>()
21374        .unwrap();
21375    pane.update(cx, |pane, cx| {
21376        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21377        open_editor.update(cx, |editor, cx| {
21378            assert_eq!(
21379                editor.display_text(cx),
21380                main_text,
21381                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21382            );
21383        })
21384    });
21385}
21386
21387#[gpui::test]
21388async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21389    struct EmptyModalView {
21390        focus_handle: gpui::FocusHandle,
21391    }
21392    impl EventEmitter<DismissEvent> for EmptyModalView {}
21393    impl Render for EmptyModalView {
21394        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21395            div()
21396        }
21397    }
21398    impl Focusable for EmptyModalView {
21399        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21400            self.focus_handle.clone()
21401        }
21402    }
21403    impl workspace::ModalView for EmptyModalView {}
21404    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21405        EmptyModalView {
21406            focus_handle: cx.focus_handle(),
21407        }
21408    }
21409
21410    init_test(cx, |_| {});
21411
21412    let fs = FakeFs::new(cx.executor());
21413    let project = Project::test(fs, [], cx).await;
21414    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21415    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21416    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21417    let editor = cx.new_window_entity(|window, cx| {
21418        Editor::new(
21419            EditorMode::full(),
21420            buffer,
21421            Some(project.clone()),
21422            window,
21423            cx,
21424        )
21425    });
21426    workspace
21427        .update(cx, |workspace, window, cx| {
21428            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21429        })
21430        .unwrap();
21431    editor.update_in(cx, |editor, window, cx| {
21432        editor.open_context_menu(&OpenContextMenu, window, cx);
21433        assert!(editor.mouse_context_menu.is_some());
21434    });
21435    workspace
21436        .update(cx, |workspace, window, cx| {
21437            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21438        })
21439        .unwrap();
21440    cx.read(|cx| {
21441        assert!(editor.read(cx).mouse_context_menu.is_none());
21442    });
21443}
21444
21445#[gpui::test]
21446async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21447    init_test(cx, |_| {});
21448
21449    let fs = FakeFs::new(cx.executor());
21450    fs.insert_file(path!("/file.html"), Default::default())
21451        .await;
21452
21453    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21454
21455    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21456    let html_language = Arc::new(Language::new(
21457        LanguageConfig {
21458            name: "HTML".into(),
21459            matcher: LanguageMatcher {
21460                path_suffixes: vec!["html".to_string()],
21461                ..LanguageMatcher::default()
21462            },
21463            brackets: BracketPairConfig {
21464                pairs: vec![BracketPair {
21465                    start: "<".into(),
21466                    end: ">".into(),
21467                    close: true,
21468                    ..Default::default()
21469                }],
21470                ..Default::default()
21471            },
21472            ..Default::default()
21473        },
21474        Some(tree_sitter_html::LANGUAGE.into()),
21475    ));
21476    language_registry.add(html_language);
21477    let mut fake_servers = language_registry.register_fake_lsp(
21478        "HTML",
21479        FakeLspAdapter {
21480            capabilities: lsp::ServerCapabilities {
21481                completion_provider: Some(lsp::CompletionOptions {
21482                    resolve_provider: Some(true),
21483                    ..Default::default()
21484                }),
21485                ..Default::default()
21486            },
21487            ..Default::default()
21488        },
21489    );
21490
21491    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21492    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21493
21494    let worktree_id = workspace
21495        .update(cx, |workspace, _window, cx| {
21496            workspace.project().update(cx, |project, cx| {
21497                project.worktrees(cx).next().unwrap().read(cx).id()
21498            })
21499        })
21500        .unwrap();
21501    project
21502        .update(cx, |project, cx| {
21503            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21504        })
21505        .await
21506        .unwrap();
21507    let editor = workspace
21508        .update(cx, |workspace, window, cx| {
21509            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21510        })
21511        .unwrap()
21512        .await
21513        .unwrap()
21514        .downcast::<Editor>()
21515        .unwrap();
21516
21517    let fake_server = fake_servers.next().await.unwrap();
21518    editor.update_in(cx, |editor, window, cx| {
21519        editor.set_text("<ad></ad>", window, cx);
21520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21521            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21522        });
21523        let Some((buffer, _)) = editor
21524            .buffer
21525            .read(cx)
21526            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21527        else {
21528            panic!("Failed to get buffer for selection position");
21529        };
21530        let buffer = buffer.read(cx);
21531        let buffer_id = buffer.remote_id();
21532        let opening_range =
21533            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21534        let closing_range =
21535            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21536        let mut linked_ranges = HashMap::default();
21537        linked_ranges.insert(
21538            buffer_id,
21539            vec![(opening_range.clone(), vec![closing_range.clone()])],
21540        );
21541        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21542    });
21543    let mut completion_handle =
21544        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21545            Ok(Some(lsp::CompletionResponse::Array(vec![
21546                lsp::CompletionItem {
21547                    label: "head".to_string(),
21548                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21549                        lsp::InsertReplaceEdit {
21550                            new_text: "head".to_string(),
21551                            insert: lsp::Range::new(
21552                                lsp::Position::new(0, 1),
21553                                lsp::Position::new(0, 3),
21554                            ),
21555                            replace: lsp::Range::new(
21556                                lsp::Position::new(0, 1),
21557                                lsp::Position::new(0, 3),
21558                            ),
21559                        },
21560                    )),
21561                    ..Default::default()
21562                },
21563            ])))
21564        });
21565    editor.update_in(cx, |editor, window, cx| {
21566        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21567    });
21568    cx.run_until_parked();
21569    completion_handle.next().await.unwrap();
21570    editor.update(cx, |editor, _| {
21571        assert!(
21572            editor.context_menu_visible(),
21573            "Completion menu should be visible"
21574        );
21575    });
21576    editor.update_in(cx, |editor, window, cx| {
21577        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21578    });
21579    cx.executor().run_until_parked();
21580    editor.update(cx, |editor, cx| {
21581        assert_eq!(editor.text(cx), "<head></head>");
21582    });
21583}
21584
21585#[gpui::test]
21586async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21587    init_test(cx, |_| {});
21588
21589    let fs = FakeFs::new(cx.executor());
21590    fs.insert_tree(
21591        path!("/root"),
21592        json!({
21593            "a": {
21594                "main.rs": "fn main() {}",
21595            },
21596            "foo": {
21597                "bar": {
21598                    "external_file.rs": "pub mod external {}",
21599                }
21600            }
21601        }),
21602    )
21603    .await;
21604
21605    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21606    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21607    language_registry.add(rust_lang());
21608    let _fake_servers = language_registry.register_fake_lsp(
21609        "Rust",
21610        FakeLspAdapter {
21611            ..FakeLspAdapter::default()
21612        },
21613    );
21614    let (workspace, cx) =
21615        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21616    let worktree_id = workspace.update(cx, |workspace, cx| {
21617        workspace.project().update(cx, |project, cx| {
21618            project.worktrees(cx).next().unwrap().read(cx).id()
21619        })
21620    });
21621
21622    let assert_language_servers_count =
21623        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21624            project.update(cx, |project, cx| {
21625                let current = project
21626                    .lsp_store()
21627                    .read(cx)
21628                    .as_local()
21629                    .unwrap()
21630                    .language_servers
21631                    .len();
21632                assert_eq!(expected, current, "{context}");
21633            });
21634        };
21635
21636    assert_language_servers_count(
21637        0,
21638        "No servers should be running before any file is open",
21639        cx,
21640    );
21641    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21642    let main_editor = workspace
21643        .update_in(cx, |workspace, window, cx| {
21644            workspace.open_path(
21645                (worktree_id, "main.rs"),
21646                Some(pane.downgrade()),
21647                true,
21648                window,
21649                cx,
21650            )
21651        })
21652        .unwrap()
21653        .await
21654        .downcast::<Editor>()
21655        .unwrap();
21656    pane.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                "fn main() {}",
21662                "Original main.rs text on initial open",
21663            );
21664        });
21665        assert_eq!(open_editor, main_editor);
21666    });
21667    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21668
21669    let external_editor = workspace
21670        .update_in(cx, |workspace, window, cx| {
21671            workspace.open_abs_path(
21672                PathBuf::from("/root/foo/bar/external_file.rs"),
21673                OpenOptions::default(),
21674                window,
21675                cx,
21676            )
21677        })
21678        .await
21679        .expect("opening external file")
21680        .downcast::<Editor>()
21681        .expect("downcasted external file's open element to editor");
21682    pane.update(cx, |pane, cx| {
21683        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21684        open_editor.update(cx, |editor, cx| {
21685            assert_eq!(
21686                editor.display_text(cx),
21687                "pub mod external {}",
21688                "External file is open now",
21689            );
21690        });
21691        assert_eq!(open_editor, external_editor);
21692    });
21693    assert_language_servers_count(
21694        1,
21695        "Second, external, *.rs file should join the existing server",
21696        cx,
21697    );
21698
21699    pane.update_in(cx, |pane, window, cx| {
21700        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21701    })
21702    .await
21703    .unwrap();
21704    pane.update_in(cx, |pane, window, cx| {
21705        pane.navigate_backward(window, cx);
21706    });
21707    cx.run_until_parked();
21708    pane.update(cx, |pane, cx| {
21709        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21710        open_editor.update(cx, |editor, cx| {
21711            assert_eq!(
21712                editor.display_text(cx),
21713                "pub mod external {}",
21714                "External file is open now",
21715            );
21716        });
21717    });
21718    assert_language_servers_count(
21719        1,
21720        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21721        cx,
21722    );
21723
21724    cx.update(|_, cx| {
21725        workspace::reload(&workspace::Reload::default(), cx);
21726    });
21727    assert_language_servers_count(
21728        1,
21729        "After reloading the worktree with local and external files opened, only one project should be started",
21730        cx,
21731    );
21732}
21733
21734#[gpui::test]
21735async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21736    init_test(cx, |_| {});
21737
21738    let mut cx = EditorTestContext::new(cx).await;
21739    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21740    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21741
21742    // test cursor move to start of each line on tab
21743    // for `if`, `elif`, `else`, `while`, `with` and `for`
21744    cx.set_state(indoc! {"
21745        def main():
21746        ˇ    for item in items:
21747        ˇ        while item.active:
21748        ˇ            if item.value > 10:
21749        ˇ                continue
21750        ˇ            elif item.value < 0:
21751        ˇ                break
21752        ˇ            else:
21753        ˇ                with item.context() as ctx:
21754        ˇ                    yield count
21755        ˇ        else:
21756        ˇ            log('while else')
21757        ˇ    else:
21758        ˇ        log('for else')
21759    "});
21760    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21761    cx.assert_editor_state(indoc! {"
21762        def main():
21763            ˇfor item in items:
21764                ˇwhile item.active:
21765                    ˇif item.value > 10:
21766                        ˇcontinue
21767                    ˇelif item.value < 0:
21768                        ˇbreak
21769                    ˇelse:
21770                        ˇwith item.context() as ctx:
21771                            ˇyield count
21772                ˇelse:
21773                    ˇlog('while else')
21774            ˇelse:
21775                ˇlog('for else')
21776    "});
21777    // test relative indent is preserved when tab
21778    // for `if`, `elif`, `else`, `while`, `with` and `for`
21779    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21780    cx.assert_editor_state(indoc! {"
21781        def main():
21782                ˇfor item in items:
21783                    ˇwhile item.active:
21784                        ˇif item.value > 10:
21785                            ˇcontinue
21786                        ˇelif item.value < 0:
21787                            ˇbreak
21788                        ˇelse:
21789                            ˇwith item.context() as ctx:
21790                                ˇyield count
21791                    ˇelse:
21792                        ˇlog('while else')
21793                ˇelse:
21794                    ˇlog('for else')
21795    "});
21796
21797    // test cursor move to start of each line on tab
21798    // for `try`, `except`, `else`, `finally`, `match` and `def`
21799    cx.set_state(indoc! {"
21800        def main():
21801        ˇ    try:
21802        ˇ        fetch()
21803        ˇ    except ValueError:
21804        ˇ        handle_error()
21805        ˇ    else:
21806        ˇ        match value:
21807        ˇ            case _:
21808        ˇ    finally:
21809        ˇ        def status():
21810        ˇ            return 0
21811    "});
21812    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21813    cx.assert_editor_state(indoc! {"
21814        def main():
21815            ˇtry:
21816                ˇfetch()
21817            ˇexcept ValueError:
21818                ˇhandle_error()
21819            ˇelse:
21820                ˇmatch value:
21821                    ˇcase _:
21822            ˇfinally:
21823                ˇdef status():
21824                    ˇreturn 0
21825    "});
21826    // test relative indent is preserved when tab
21827    // for `try`, `except`, `else`, `finally`, `match` and `def`
21828    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21829    cx.assert_editor_state(indoc! {"
21830        def main():
21831                ˇtry:
21832                    ˇfetch()
21833                ˇexcept ValueError:
21834                    ˇhandle_error()
21835                ˇelse:
21836                    ˇmatch value:
21837                        ˇcase _:
21838                ˇfinally:
21839                    ˇdef status():
21840                        ˇreturn 0
21841    "});
21842}
21843
21844#[gpui::test]
21845async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21846    init_test(cx, |_| {});
21847
21848    let mut cx = EditorTestContext::new(cx).await;
21849    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21850    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21851
21852    // test `else` auto outdents when typed inside `if` block
21853    cx.set_state(indoc! {"
21854        def main():
21855            if i == 2:
21856                return
21857                ˇ
21858    "});
21859    cx.update_editor(|editor, window, cx| {
21860        editor.handle_input("else:", window, cx);
21861    });
21862    cx.assert_editor_state(indoc! {"
21863        def main():
21864            if i == 2:
21865                return
21866            else:ˇ
21867    "});
21868
21869    // test `except` auto outdents when typed inside `try` block
21870    cx.set_state(indoc! {"
21871        def main():
21872            try:
21873                i = 2
21874                ˇ
21875    "});
21876    cx.update_editor(|editor, window, cx| {
21877        editor.handle_input("except:", window, cx);
21878    });
21879    cx.assert_editor_state(indoc! {"
21880        def main():
21881            try:
21882                i = 2
21883            except:ˇ
21884    "});
21885
21886    // test `else` auto outdents when typed inside `except` block
21887    cx.set_state(indoc! {"
21888        def main():
21889            try:
21890                i = 2
21891            except:
21892                j = 2
21893                ˇ
21894    "});
21895    cx.update_editor(|editor, window, cx| {
21896        editor.handle_input("else:", window, cx);
21897    });
21898    cx.assert_editor_state(indoc! {"
21899        def main():
21900            try:
21901                i = 2
21902            except:
21903                j = 2
21904            else:ˇ
21905    "});
21906
21907    // test `finally` auto outdents when typed inside `else` block
21908    cx.set_state(indoc! {"
21909        def main():
21910            try:
21911                i = 2
21912            except:
21913                j = 2
21914            else:
21915                k = 2
21916                ˇ
21917    "});
21918    cx.update_editor(|editor, window, cx| {
21919        editor.handle_input("finally:", window, cx);
21920    });
21921    cx.assert_editor_state(indoc! {"
21922        def main():
21923            try:
21924                i = 2
21925            except:
21926                j = 2
21927            else:
21928                k = 2
21929            finally:ˇ
21930    "});
21931
21932    // test `else` does not outdents when typed inside `except` block right after for block
21933    cx.set_state(indoc! {"
21934        def main():
21935            try:
21936                i = 2
21937            except:
21938                for i in range(n):
21939                    pass
21940                ˇ
21941    "});
21942    cx.update_editor(|editor, window, cx| {
21943        editor.handle_input("else:", window, cx);
21944    });
21945    cx.assert_editor_state(indoc! {"
21946        def main():
21947            try:
21948                i = 2
21949            except:
21950                for i in range(n):
21951                    pass
21952                else:ˇ
21953    "});
21954
21955    // test `finally` auto outdents when typed inside `else` block right after for block
21956    cx.set_state(indoc! {"
21957        def main():
21958            try:
21959                i = 2
21960            except:
21961                j = 2
21962            else:
21963                for i in range(n):
21964                    pass
21965                ˇ
21966    "});
21967    cx.update_editor(|editor, window, cx| {
21968        editor.handle_input("finally:", window, cx);
21969    });
21970    cx.assert_editor_state(indoc! {"
21971        def main():
21972            try:
21973                i = 2
21974            except:
21975                j = 2
21976            else:
21977                for i in range(n):
21978                    pass
21979            finally:ˇ
21980    "});
21981
21982    // test `except` outdents to inner "try" block
21983    cx.set_state(indoc! {"
21984        def main():
21985            try:
21986                i = 2
21987                if i == 2:
21988                    try:
21989                        i = 3
21990                        ˇ
21991    "});
21992    cx.update_editor(|editor, window, cx| {
21993        editor.handle_input("except:", window, cx);
21994    });
21995    cx.assert_editor_state(indoc! {"
21996        def main():
21997            try:
21998                i = 2
21999                if i == 2:
22000                    try:
22001                        i = 3
22002                    except:ˇ
22003    "});
22004
22005    // test `except` outdents to outer "try" block
22006    cx.set_state(indoc! {"
22007        def main():
22008            try:
22009                i = 2
22010                if i == 2:
22011                    try:
22012                        i = 3
22013                ˇ
22014    "});
22015    cx.update_editor(|editor, window, cx| {
22016        editor.handle_input("except:", window, cx);
22017    });
22018    cx.assert_editor_state(indoc! {"
22019        def main():
22020            try:
22021                i = 2
22022                if i == 2:
22023                    try:
22024                        i = 3
22025            except:ˇ
22026    "});
22027
22028    // test `else` stays at correct indent when typed after `for` block
22029    cx.set_state(indoc! {"
22030        def main():
22031            for i in range(10):
22032                if i == 3:
22033                    break
22034            ˇ
22035    "});
22036    cx.update_editor(|editor, window, cx| {
22037        editor.handle_input("else:", window, cx);
22038    });
22039    cx.assert_editor_state(indoc! {"
22040        def main():
22041            for i in range(10):
22042                if i == 3:
22043                    break
22044            else:ˇ
22045    "});
22046
22047    // test does not outdent on typing after line with square brackets
22048    cx.set_state(indoc! {"
22049        def f() -> list[str]:
22050            ˇ
22051    "});
22052    cx.update_editor(|editor, window, cx| {
22053        editor.handle_input("a", window, cx);
22054    });
22055    cx.assert_editor_state(indoc! {"
22056        def f() -> list[str]:
2205722058    "});
22059}
22060
22061#[gpui::test]
22062async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22063    init_test(cx, |_| {});
22064    update_test_language_settings(cx, |settings| {
22065        settings.defaults.extend_comment_on_newline = Some(false);
22066    });
22067    let mut cx = EditorTestContext::new(cx).await;
22068    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22069    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22070
22071    // test correct indent after newline on comment
22072    cx.set_state(indoc! {"
22073        # COMMENT:ˇ
22074    "});
22075    cx.update_editor(|editor, window, cx| {
22076        editor.newline(&Newline, window, cx);
22077    });
22078    cx.assert_editor_state(indoc! {"
22079        # COMMENT:
22080        ˇ
22081    "});
22082
22083    // test correct indent after newline in brackets
22084    cx.set_state(indoc! {"
22085        {ˇ}
22086    "});
22087    cx.update_editor(|editor, window, cx| {
22088        editor.newline(&Newline, window, cx);
22089    });
22090    cx.run_until_parked();
22091    cx.assert_editor_state(indoc! {"
22092        {
22093            ˇ
22094        }
22095    "});
22096
22097    cx.set_state(indoc! {"
22098        (ˇ)
22099    "});
22100    cx.update_editor(|editor, window, cx| {
22101        editor.newline(&Newline, window, cx);
22102    });
22103    cx.run_until_parked();
22104    cx.assert_editor_state(indoc! {"
22105        (
22106            ˇ
22107        )
22108    "});
22109
22110    // do not indent after empty lists or dictionaries
22111    cx.set_state(indoc! {"
22112        a = []ˇ
22113    "});
22114    cx.update_editor(|editor, window, cx| {
22115        editor.newline(&Newline, window, cx);
22116    });
22117    cx.run_until_parked();
22118    cx.assert_editor_state(indoc! {"
22119        a = []
22120        ˇ
22121    "});
22122}
22123
22124fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22125    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22126    point..point
22127}
22128
22129#[track_caller]
22130fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22131    let (text, ranges) = marked_text_ranges(marked_text, true);
22132    assert_eq!(editor.text(cx), text);
22133    assert_eq!(
22134        editor.selections.ranges(cx),
22135        ranges,
22136        "Assert selections are {}",
22137        marked_text
22138    );
22139}
22140
22141pub fn handle_signature_help_request(
22142    cx: &mut EditorLspTestContext,
22143    mocked_response: lsp::SignatureHelp,
22144) -> impl Future<Output = ()> + use<> {
22145    let mut request =
22146        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22147            let mocked_response = mocked_response.clone();
22148            async move { Ok(Some(mocked_response)) }
22149        });
22150
22151    async move {
22152        request.next().await;
22153    }
22154}
22155
22156#[track_caller]
22157pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22158    cx.update_editor(|editor, _, _| {
22159        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22160            let entries = menu.entries.borrow();
22161            let entries = entries
22162                .iter()
22163                .map(|entry| entry.string.as_str())
22164                .collect::<Vec<_>>();
22165            assert_eq!(entries, expected);
22166        } else {
22167            panic!("Expected completions menu");
22168        }
22169    });
22170}
22171
22172/// Handle completion request passing a marked string specifying where the completion
22173/// should be triggered from using '|' character, what range should be replaced, and what completions
22174/// should be returned using '<' and '>' to delimit the range.
22175///
22176/// Also see `handle_completion_request_with_insert_and_replace`.
22177#[track_caller]
22178pub fn handle_completion_request(
22179    marked_string: &str,
22180    completions: Vec<&'static str>,
22181    is_incomplete: bool,
22182    counter: Arc<AtomicUsize>,
22183    cx: &mut EditorLspTestContext,
22184) -> impl Future<Output = ()> {
22185    let complete_from_marker: TextRangeMarker = '|'.into();
22186    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22187    let (_, mut marked_ranges) = marked_text_ranges_by(
22188        marked_string,
22189        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22190    );
22191
22192    let complete_from_position =
22193        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22194    let replace_range =
22195        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22196
22197    let mut request =
22198        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22199            let completions = completions.clone();
22200            counter.fetch_add(1, atomic::Ordering::Release);
22201            async move {
22202                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22203                assert_eq!(
22204                    params.text_document_position.position,
22205                    complete_from_position
22206                );
22207                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22208                    is_incomplete: is_incomplete,
22209                    item_defaults: None,
22210                    items: completions
22211                        .iter()
22212                        .map(|completion_text| lsp::CompletionItem {
22213                            label: completion_text.to_string(),
22214                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22215                                range: replace_range,
22216                                new_text: completion_text.to_string(),
22217                            })),
22218                            ..Default::default()
22219                        })
22220                        .collect(),
22221                })))
22222            }
22223        });
22224
22225    async move {
22226        request.next().await;
22227    }
22228}
22229
22230/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22231/// given instead, which also contains an `insert` range.
22232///
22233/// This function uses markers to define ranges:
22234/// - `|` marks the cursor position
22235/// - `<>` marks the replace range
22236/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22237pub fn handle_completion_request_with_insert_and_replace(
22238    cx: &mut EditorLspTestContext,
22239    marked_string: &str,
22240    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22241    counter: Arc<AtomicUsize>,
22242) -> impl Future<Output = ()> {
22243    let complete_from_marker: TextRangeMarker = '|'.into();
22244    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22245    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22246
22247    let (_, mut marked_ranges) = marked_text_ranges_by(
22248        marked_string,
22249        vec![
22250            complete_from_marker.clone(),
22251            replace_range_marker.clone(),
22252            insert_range_marker.clone(),
22253        ],
22254    );
22255
22256    let complete_from_position =
22257        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22258    let replace_range =
22259        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22260
22261    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22262        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22263        _ => lsp::Range {
22264            start: replace_range.start,
22265            end: complete_from_position,
22266        },
22267    };
22268
22269    let mut request =
22270        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22271            let completions = completions.clone();
22272            counter.fetch_add(1, atomic::Ordering::Release);
22273            async move {
22274                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22275                assert_eq!(
22276                    params.text_document_position.position, complete_from_position,
22277                    "marker `|` position doesn't match",
22278                );
22279                Ok(Some(lsp::CompletionResponse::Array(
22280                    completions
22281                        .iter()
22282                        .map(|(label, new_text)| lsp::CompletionItem {
22283                            label: label.to_string(),
22284                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22285                                lsp::InsertReplaceEdit {
22286                                    insert: insert_range,
22287                                    replace: replace_range,
22288                                    new_text: new_text.to_string(),
22289                                },
22290                            )),
22291                            ..Default::default()
22292                        })
22293                        .collect(),
22294                )))
22295            }
22296        });
22297
22298    async move {
22299        request.next().await;
22300    }
22301}
22302
22303fn handle_resolve_completion_request(
22304    cx: &mut EditorLspTestContext,
22305    edits: Option<Vec<(&'static str, &'static str)>>,
22306) -> impl Future<Output = ()> {
22307    let edits = edits.map(|edits| {
22308        edits
22309            .iter()
22310            .map(|(marked_string, new_text)| {
22311                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22312                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22313                lsp::TextEdit::new(replace_range, new_text.to_string())
22314            })
22315            .collect::<Vec<_>>()
22316    });
22317
22318    let mut request =
22319        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22320            let edits = edits.clone();
22321            async move {
22322                Ok(lsp::CompletionItem {
22323                    additional_text_edits: edits,
22324                    ..Default::default()
22325                })
22326            }
22327        });
22328
22329    async move {
22330        request.next().await;
22331    }
22332}
22333
22334pub(crate) fn update_test_language_settings(
22335    cx: &mut TestAppContext,
22336    f: impl Fn(&mut AllLanguageSettingsContent),
22337) {
22338    cx.update(|cx| {
22339        SettingsStore::update_global(cx, |store, cx| {
22340            store.update_user_settings::<AllLanguageSettings>(cx, f);
22341        });
22342    });
22343}
22344
22345pub(crate) fn update_test_project_settings(
22346    cx: &mut TestAppContext,
22347    f: impl Fn(&mut ProjectSettings),
22348) {
22349    cx.update(|cx| {
22350        SettingsStore::update_global(cx, |store, cx| {
22351            store.update_user_settings::<ProjectSettings>(cx, f);
22352        });
22353    });
22354}
22355
22356pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22357    cx.update(|cx| {
22358        assets::Assets.load_test_fonts(cx);
22359        let store = SettingsStore::test(cx);
22360        cx.set_global(store);
22361        theme::init(theme::LoadThemes::JustBase, cx);
22362        release_channel::init(SemanticVersion::default(), cx);
22363        client::init_settings(cx);
22364        language::init(cx);
22365        Project::init_settings(cx);
22366        workspace::init_settings(cx);
22367        crate::init(cx);
22368    });
22369
22370    update_test_language_settings(cx, f);
22371}
22372
22373#[track_caller]
22374fn assert_hunk_revert(
22375    not_reverted_text_with_selections: &str,
22376    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22377    expected_reverted_text_with_selections: &str,
22378    base_text: &str,
22379    cx: &mut EditorLspTestContext,
22380) {
22381    cx.set_state(not_reverted_text_with_selections);
22382    cx.set_head_text(base_text);
22383    cx.executor().run_until_parked();
22384
22385    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22386        let snapshot = editor.snapshot(window, cx);
22387        let reverted_hunk_statuses = snapshot
22388            .buffer_snapshot
22389            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22390            .map(|hunk| hunk.status().kind)
22391            .collect::<Vec<_>>();
22392
22393        editor.git_restore(&Default::default(), window, cx);
22394        reverted_hunk_statuses
22395    });
22396    cx.executor().run_until_parked();
22397    cx.assert_editor_state(expected_reverted_text_with_selections);
22398    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22399}
22400
22401#[gpui::test(iterations = 10)]
22402async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22403    init_test(cx, |_| {});
22404
22405    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22406    let counter = diagnostic_requests.clone();
22407
22408    let fs = FakeFs::new(cx.executor());
22409    fs.insert_tree(
22410        path!("/a"),
22411        json!({
22412            "first.rs": "fn main() { let a = 5; }",
22413            "second.rs": "// Test file",
22414        }),
22415    )
22416    .await;
22417
22418    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22419    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22420    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22421
22422    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22423    language_registry.add(rust_lang());
22424    let mut fake_servers = language_registry.register_fake_lsp(
22425        "Rust",
22426        FakeLspAdapter {
22427            capabilities: lsp::ServerCapabilities {
22428                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22429                    lsp::DiagnosticOptions {
22430                        identifier: None,
22431                        inter_file_dependencies: true,
22432                        workspace_diagnostics: true,
22433                        work_done_progress_options: Default::default(),
22434                    },
22435                )),
22436                ..Default::default()
22437            },
22438            ..Default::default()
22439        },
22440    );
22441
22442    let editor = workspace
22443        .update(cx, |workspace, window, cx| {
22444            workspace.open_abs_path(
22445                PathBuf::from(path!("/a/first.rs")),
22446                OpenOptions::default(),
22447                window,
22448                cx,
22449            )
22450        })
22451        .unwrap()
22452        .await
22453        .unwrap()
22454        .downcast::<Editor>()
22455        .unwrap();
22456    let fake_server = fake_servers.next().await.unwrap();
22457    let server_id = fake_server.server.server_id();
22458    let mut first_request = fake_server
22459        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22460            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22461            let result_id = Some(new_result_id.to_string());
22462            assert_eq!(
22463                params.text_document.uri,
22464                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22465            );
22466            async move {
22467                Ok(lsp::DocumentDiagnosticReportResult::Report(
22468                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22469                        related_documents: None,
22470                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22471                            items: Vec::new(),
22472                            result_id,
22473                        },
22474                    }),
22475                ))
22476            }
22477        });
22478
22479    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22480        project.update(cx, |project, cx| {
22481            let buffer_id = editor
22482                .read(cx)
22483                .buffer()
22484                .read(cx)
22485                .as_singleton()
22486                .expect("created a singleton buffer")
22487                .read(cx)
22488                .remote_id();
22489            let buffer_result_id = project
22490                .lsp_store()
22491                .read(cx)
22492                .result_id(server_id, buffer_id, cx);
22493            assert_eq!(expected, buffer_result_id);
22494        });
22495    };
22496
22497    ensure_result_id(None, cx);
22498    cx.executor().advance_clock(Duration::from_millis(60));
22499    cx.executor().run_until_parked();
22500    assert_eq!(
22501        diagnostic_requests.load(atomic::Ordering::Acquire),
22502        1,
22503        "Opening file should trigger diagnostic request"
22504    );
22505    first_request
22506        .next()
22507        .await
22508        .expect("should have sent the first diagnostics pull request");
22509    ensure_result_id(Some("1".to_string()), cx);
22510
22511    // Editing should trigger diagnostics
22512    editor.update_in(cx, |editor, window, cx| {
22513        editor.handle_input("2", window, cx)
22514    });
22515    cx.executor().advance_clock(Duration::from_millis(60));
22516    cx.executor().run_until_parked();
22517    assert_eq!(
22518        diagnostic_requests.load(atomic::Ordering::Acquire),
22519        2,
22520        "Editing should trigger diagnostic request"
22521    );
22522    ensure_result_id(Some("2".to_string()), cx);
22523
22524    // Moving cursor should not trigger diagnostic request
22525    editor.update_in(cx, |editor, window, cx| {
22526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22527            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22528        });
22529    });
22530    cx.executor().advance_clock(Duration::from_millis(60));
22531    cx.executor().run_until_parked();
22532    assert_eq!(
22533        diagnostic_requests.load(atomic::Ordering::Acquire),
22534        2,
22535        "Cursor movement should not trigger diagnostic request"
22536    );
22537    ensure_result_id(Some("2".to_string()), cx);
22538    // Multiple rapid edits should be debounced
22539    for _ in 0..5 {
22540        editor.update_in(cx, |editor, window, cx| {
22541            editor.handle_input("x", window, cx)
22542        });
22543    }
22544    cx.executor().advance_clock(Duration::from_millis(60));
22545    cx.executor().run_until_parked();
22546
22547    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22548    assert!(
22549        final_requests <= 4,
22550        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22551    );
22552    ensure_result_id(Some(final_requests.to_string()), cx);
22553}
22554
22555#[gpui::test]
22556async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22557    // Regression test for issue #11671
22558    // Previously, adding a cursor after moving multiple cursors would reset
22559    // the cursor count instead of adding to the existing cursors.
22560    init_test(cx, |_| {});
22561    let mut cx = EditorTestContext::new(cx).await;
22562
22563    // Create a simple buffer with cursor at start
22564    cx.set_state(indoc! {"
22565        ˇaaaa
22566        bbbb
22567        cccc
22568        dddd
22569        eeee
22570        ffff
22571        gggg
22572        hhhh"});
22573
22574    // Add 2 cursors below (so we have 3 total)
22575    cx.update_editor(|editor, window, cx| {
22576        editor.add_selection_below(&Default::default(), window, cx);
22577        editor.add_selection_below(&Default::default(), window, cx);
22578    });
22579
22580    // Verify we have 3 cursors
22581    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22582    assert_eq!(
22583        initial_count, 3,
22584        "Should have 3 cursors after adding 2 below"
22585    );
22586
22587    // Move down one line
22588    cx.update_editor(|editor, window, cx| {
22589        editor.move_down(&MoveDown, window, cx);
22590    });
22591
22592    // Add another cursor below
22593    cx.update_editor(|editor, window, cx| {
22594        editor.add_selection_below(&Default::default(), window, cx);
22595    });
22596
22597    // Should now have 4 cursors (3 original + 1 new)
22598    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22599    assert_eq!(
22600        final_count, 4,
22601        "Should have 4 cursors after moving and adding another"
22602    );
22603}
22604
22605#[gpui::test(iterations = 10)]
22606async fn test_document_colors(cx: &mut TestAppContext) {
22607    let expected_color = Rgba {
22608        r: 0.33,
22609        g: 0.33,
22610        b: 0.33,
22611        a: 0.33,
22612    };
22613
22614    init_test(cx, |_| {});
22615
22616    let fs = FakeFs::new(cx.executor());
22617    fs.insert_tree(
22618        path!("/a"),
22619        json!({
22620            "first.rs": "fn main() { let a = 5; }",
22621        }),
22622    )
22623    .await;
22624
22625    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22626    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22627    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22628
22629    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22630    language_registry.add(rust_lang());
22631    let mut fake_servers = language_registry.register_fake_lsp(
22632        "Rust",
22633        FakeLspAdapter {
22634            capabilities: lsp::ServerCapabilities {
22635                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22636                ..lsp::ServerCapabilities::default()
22637            },
22638            name: "rust-analyzer",
22639            ..FakeLspAdapter::default()
22640        },
22641    );
22642    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22643        "Rust",
22644        FakeLspAdapter {
22645            capabilities: lsp::ServerCapabilities {
22646                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22647                ..lsp::ServerCapabilities::default()
22648            },
22649            name: "not-rust-analyzer",
22650            ..FakeLspAdapter::default()
22651        },
22652    );
22653
22654    let editor = workspace
22655        .update(cx, |workspace, window, cx| {
22656            workspace.open_abs_path(
22657                PathBuf::from(path!("/a/first.rs")),
22658                OpenOptions::default(),
22659                window,
22660                cx,
22661            )
22662        })
22663        .unwrap()
22664        .await
22665        .unwrap()
22666        .downcast::<Editor>()
22667        .unwrap();
22668    let fake_language_server = fake_servers.next().await.unwrap();
22669    let fake_language_server_without_capabilities =
22670        fake_servers_without_capabilities.next().await.unwrap();
22671    let requests_made = Arc::new(AtomicUsize::new(0));
22672    let closure_requests_made = Arc::clone(&requests_made);
22673    let mut color_request_handle = fake_language_server
22674        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22675            let requests_made = Arc::clone(&closure_requests_made);
22676            async move {
22677                assert_eq!(
22678                    params.text_document.uri,
22679                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22680                );
22681                requests_made.fetch_add(1, atomic::Ordering::Release);
22682                Ok(vec![
22683                    lsp::ColorInformation {
22684                        range: lsp::Range {
22685                            start: lsp::Position {
22686                                line: 0,
22687                                character: 0,
22688                            },
22689                            end: lsp::Position {
22690                                line: 0,
22691                                character: 1,
22692                            },
22693                        },
22694                        color: lsp::Color {
22695                            red: 0.33,
22696                            green: 0.33,
22697                            blue: 0.33,
22698                            alpha: 0.33,
22699                        },
22700                    },
22701                    lsp::ColorInformation {
22702                        range: lsp::Range {
22703                            start: lsp::Position {
22704                                line: 0,
22705                                character: 0,
22706                            },
22707                            end: lsp::Position {
22708                                line: 0,
22709                                character: 1,
22710                            },
22711                        },
22712                        color: lsp::Color {
22713                            red: 0.33,
22714                            green: 0.33,
22715                            blue: 0.33,
22716                            alpha: 0.33,
22717                        },
22718                    },
22719                ])
22720            }
22721        });
22722
22723    let _handle = fake_language_server_without_capabilities
22724        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22725            panic!("Should not be called");
22726        });
22727    cx.executor().advance_clock(Duration::from_millis(100));
22728    color_request_handle.next().await.unwrap();
22729    cx.run_until_parked();
22730    assert_eq!(
22731        1,
22732        requests_made.load(atomic::Ordering::Acquire),
22733        "Should query for colors once per editor open"
22734    );
22735    editor.update_in(cx, |editor, _, cx| {
22736        assert_eq!(
22737            vec![expected_color],
22738            extract_color_inlays(editor, cx),
22739            "Should have an initial inlay"
22740        );
22741    });
22742
22743    // opening another file in a split should not influence the LSP query counter
22744    workspace
22745        .update(cx, |workspace, window, cx| {
22746            assert_eq!(
22747                workspace.panes().len(),
22748                1,
22749                "Should have one pane with one editor"
22750            );
22751            workspace.move_item_to_pane_in_direction(
22752                &MoveItemToPaneInDirection {
22753                    direction: SplitDirection::Right,
22754                    focus: false,
22755                    clone: true,
22756                },
22757                window,
22758                cx,
22759            );
22760        })
22761        .unwrap();
22762    cx.run_until_parked();
22763    workspace
22764        .update(cx, |workspace, _, cx| {
22765            let panes = workspace.panes();
22766            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
22767            for pane in panes {
22768                let editor = pane
22769                    .read(cx)
22770                    .active_item()
22771                    .and_then(|item| item.downcast::<Editor>())
22772                    .expect("Should have opened an editor in each split");
22773                let editor_file = editor
22774                    .read(cx)
22775                    .buffer()
22776                    .read(cx)
22777                    .as_singleton()
22778                    .expect("test deals with singleton buffers")
22779                    .read(cx)
22780                    .file()
22781                    .expect("test buffese should have a file")
22782                    .path();
22783                assert_eq!(
22784                    editor_file.as_ref(),
22785                    Path::new("first.rs"),
22786                    "Both editors should be opened for the same file"
22787                )
22788            }
22789        })
22790        .unwrap();
22791
22792    cx.executor().advance_clock(Duration::from_millis(500));
22793    let save = editor.update_in(cx, |editor, window, cx| {
22794        editor.move_to_end(&MoveToEnd, window, cx);
22795        editor.handle_input("dirty", window, cx);
22796        editor.save(
22797            SaveOptions {
22798                format: true,
22799                autosave: true,
22800            },
22801            project.clone(),
22802            window,
22803            cx,
22804        )
22805    });
22806    save.await.unwrap();
22807
22808    color_request_handle.next().await.unwrap();
22809    cx.run_until_parked();
22810    assert_eq!(
22811        3,
22812        requests_made.load(atomic::Ordering::Acquire),
22813        "Should query for colors once per save and once per formatting after save"
22814    );
22815
22816    drop(editor);
22817    let close = workspace
22818        .update(cx, |workspace, window, cx| {
22819            workspace.active_pane().update(cx, |pane, cx| {
22820                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22821            })
22822        })
22823        .unwrap();
22824    close.await.unwrap();
22825    let close = workspace
22826        .update(cx, |workspace, window, cx| {
22827            workspace.active_pane().update(cx, |pane, cx| {
22828                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22829            })
22830        })
22831        .unwrap();
22832    close.await.unwrap();
22833    assert_eq!(
22834        3,
22835        requests_made.load(atomic::Ordering::Acquire),
22836        "After saving and closing all editors, no extra requests should be made"
22837    );
22838    workspace
22839        .update(cx, |workspace, _, cx| {
22840            assert!(
22841                workspace.active_item(cx).is_none(),
22842                "Should close all editors"
22843            )
22844        })
22845        .unwrap();
22846
22847    workspace
22848        .update(cx, |workspace, window, cx| {
22849            workspace.active_pane().update(cx, |pane, cx| {
22850                pane.navigate_backward(window, cx);
22851            })
22852        })
22853        .unwrap();
22854    cx.executor().advance_clock(Duration::from_millis(100));
22855    cx.run_until_parked();
22856    let editor = workspace
22857        .update(cx, |workspace, _, cx| {
22858            workspace
22859                .active_item(cx)
22860                .expect("Should have reopened the editor again after navigating back")
22861                .downcast::<Editor>()
22862                .expect("Should be an editor")
22863        })
22864        .unwrap();
22865    color_request_handle.next().await.unwrap();
22866    assert_eq!(
22867        3,
22868        requests_made.load(atomic::Ordering::Acquire),
22869        "Cache should be reused on buffer close and reopen"
22870    );
22871    editor.update(cx, |editor, cx| {
22872        assert_eq!(
22873            vec![expected_color],
22874            extract_color_inlays(editor, cx),
22875            "Should have an initial inlay"
22876        );
22877    });
22878}
22879
22880#[gpui::test]
22881async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
22882    init_test(cx, |_| {});
22883    let (editor, cx) = cx.add_window_view(Editor::single_line);
22884    editor.update_in(cx, |editor, window, cx| {
22885        editor.set_text("oops\n\nwow\n", window, cx)
22886    });
22887    cx.run_until_parked();
22888    editor.update(cx, |editor, cx| {
22889        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
22890    });
22891    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
22892    cx.run_until_parked();
22893    editor.update(cx, |editor, cx| {
22894        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
22895    });
22896}
22897
22898#[track_caller]
22899fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22900    editor
22901        .all_inlays(cx)
22902        .into_iter()
22903        .filter_map(|inlay| inlay.get_color())
22904        .map(Rgba::from)
22905        .collect()
22906}