editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let move_to_beg = MoveToBeginningOfLine {
 1909        stop_at_soft_wraps: true,
 1910        stop_at_indent: true,
 1911    };
 1912
 1913    let editor = cx.add_window(|window, cx| {
 1914        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1915        build_editor(buffer, window, cx)
 1916    });
 1917
 1918    _ = editor.update(cx, |editor, window, cx| {
 1919        // test cursor between line_start and indent_start
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1923            ]);
 1924        });
 1925
 1926        // cursor should move to line_start
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            editor.selections.display_ranges(cx),
 1930            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1931        );
 1932
 1933        // cursor should move to indent_start
 1934        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1935        assert_eq!(
 1936            editor.selections.display_ranges(cx),
 1937            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1938        );
 1939
 1940        // cursor should move to back to line_start
 1941        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1942        assert_eq!(
 1943            editor.selections.display_ranges(cx),
 1944            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1945        );
 1946    });
 1947}
 1948
 1949#[gpui::test]
 1950fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1951    init_test(cx, |_| {});
 1952
 1953    let editor = cx.add_window(|window, cx| {
 1954        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1955        build_editor(buffer, window, cx)
 1956    });
 1957    _ = editor.update(cx, |editor, window, cx| {
 1958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1959            s.select_display_ranges([
 1960                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1961                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1962            ])
 1963        });
 1964        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1965        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1966
 1967        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1968        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1969
 1970        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1971        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1972
 1973        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1974        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1975
 1976        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1977        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1978
 1979        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1980        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1981
 1982        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1983        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1987
 1988        editor.move_right(&MoveRight, window, cx);
 1989        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1990        assert_selection_ranges(
 1991            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1992            editor,
 1993            cx,
 1994        );
 1995
 1996        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1997        assert_selection_ranges(
 1998            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1999            editor,
 2000            cx,
 2001        );
 2002
 2003        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2004        assert_selection_ranges(
 2005            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2006            editor,
 2007            cx,
 2008        );
 2009    });
 2010}
 2011
 2012#[gpui::test]
 2013fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2014    init_test(cx, |_| {});
 2015
 2016    let editor = cx.add_window(|window, cx| {
 2017        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2018        build_editor(buffer, window, cx)
 2019    });
 2020
 2021    _ = editor.update(cx, |editor, window, cx| {
 2022        editor.set_wrap_width(Some(140.0.into()), cx);
 2023        assert_eq!(
 2024            editor.display_text(cx),
 2025            "use one::{\n    two::three::\n    four::five\n};"
 2026        );
 2027
 2028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2029            s.select_display_ranges([
 2030                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2031            ]);
 2032        });
 2033
 2034        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2035        assert_eq!(
 2036            editor.selections.display_ranges(cx),
 2037            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2038        );
 2039
 2040        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2041        assert_eq!(
 2042            editor.selections.display_ranges(cx),
 2043            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2044        );
 2045
 2046        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2047        assert_eq!(
 2048            editor.selections.display_ranges(cx),
 2049            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2050        );
 2051
 2052        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2056        );
 2057
 2058        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2059        assert_eq!(
 2060            editor.selections.display_ranges(cx),
 2061            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2062        );
 2063
 2064        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2065        assert_eq!(
 2066            editor.selections.display_ranges(cx),
 2067            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2068        );
 2069    });
 2070}
 2071
 2072#[gpui::test]
 2073async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2074    init_test(cx, |_| {});
 2075    let mut cx = EditorTestContext::new(cx).await;
 2076
 2077    let line_height = cx.editor(|editor, window, _| {
 2078        editor
 2079            .style()
 2080            .unwrap()
 2081            .text
 2082            .line_height_in_pixels(window.rem_size())
 2083    });
 2084    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2085
 2086    cx.set_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_end_of_paragraph(&MoveToEndOfParagraph, 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_end_of_paragraph(&MoveToEndOfParagraph, 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_end_of_paragraph(&MoveToEndOfParagraph, 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    cx.update_editor(|editor, window, cx| {
 2144        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2145    });
 2146    cx.assert_editor_state(
 2147        &r#"one
 2148        two
 2149
 2150        three
 2151        four
 2152        five
 2153        ˇ
 2154        six"#
 2155            .unindent(),
 2156    );
 2157
 2158    cx.update_editor(|editor, window, cx| {
 2159        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2160    });
 2161    cx.assert_editor_state(
 2162        &r#"one
 2163        two
 2164        ˇ
 2165        three
 2166        four
 2167        five
 2168
 2169        six"#
 2170            .unindent(),
 2171    );
 2172
 2173    cx.update_editor(|editor, window, cx| {
 2174        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2175    });
 2176    cx.assert_editor_state(
 2177        &r#"ˇone
 2178        two
 2179
 2180        three
 2181        four
 2182        five
 2183
 2184        six"#
 2185            .unindent(),
 2186    );
 2187}
 2188
 2189#[gpui::test]
 2190async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192    let mut cx = EditorTestContext::new(cx).await;
 2193    let line_height = cx.editor(|editor, window, _| {
 2194        editor
 2195            .style()
 2196            .unwrap()
 2197            .text
 2198            .line_height_in_pixels(window.rem_size())
 2199    });
 2200    let window = cx.window;
 2201    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2202
 2203    cx.set_state(
 2204        r#"ˇone
 2205        two
 2206        three
 2207        four
 2208        five
 2209        six
 2210        seven
 2211        eight
 2212        nine
 2213        ten
 2214        "#,
 2215    );
 2216
 2217    cx.update_editor(|editor, window, cx| {
 2218        assert_eq!(
 2219            editor.snapshot(window, cx).scroll_position(),
 2220            gpui::Point::new(0., 0.)
 2221        );
 2222        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2223        assert_eq!(
 2224            editor.snapshot(window, cx).scroll_position(),
 2225            gpui::Point::new(0., 3.)
 2226        );
 2227        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2228        assert_eq!(
 2229            editor.snapshot(window, cx).scroll_position(),
 2230            gpui::Point::new(0., 6.)
 2231        );
 2232        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2233        assert_eq!(
 2234            editor.snapshot(window, cx).scroll_position(),
 2235            gpui::Point::new(0., 3.)
 2236        );
 2237
 2238        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2239        assert_eq!(
 2240            editor.snapshot(window, cx).scroll_position(),
 2241            gpui::Point::new(0., 1.)
 2242        );
 2243        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2244        assert_eq!(
 2245            editor.snapshot(window, cx).scroll_position(),
 2246            gpui::Point::new(0., 3.)
 2247        );
 2248    });
 2249}
 2250
 2251#[gpui::test]
 2252async fn test_autoscroll(cx: &mut TestAppContext) {
 2253    init_test(cx, |_| {});
 2254    let mut cx = EditorTestContext::new(cx).await;
 2255
 2256    let line_height = cx.update_editor(|editor, window, cx| {
 2257        editor.set_vertical_scroll_margin(2, cx);
 2258        editor
 2259            .style()
 2260            .unwrap()
 2261            .text
 2262            .line_height_in_pixels(window.rem_size())
 2263    });
 2264    let window = cx.window;
 2265    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2266
 2267    cx.set_state(
 2268        r#"ˇone
 2269            two
 2270            three
 2271            four
 2272            five
 2273            six
 2274            seven
 2275            eight
 2276            nine
 2277            ten
 2278        "#,
 2279    );
 2280    cx.update_editor(|editor, window, cx| {
 2281        assert_eq!(
 2282            editor.snapshot(window, cx).scroll_position(),
 2283            gpui::Point::new(0., 0.0)
 2284        );
 2285    });
 2286
 2287    // Add a cursor below the visible area. Since both cursors cannot fit
 2288    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2289    // allows the vertical scroll margin below that cursor.
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.change_selections(Default::default(), window, cx, |selections| {
 2292            selections.select_ranges([
 2293                Point::new(0, 0)..Point::new(0, 0),
 2294                Point::new(6, 0)..Point::new(6, 0),
 2295            ]);
 2296        })
 2297    });
 2298    cx.update_editor(|editor, window, cx| {
 2299        assert_eq!(
 2300            editor.snapshot(window, cx).scroll_position(),
 2301            gpui::Point::new(0., 3.0)
 2302        );
 2303    });
 2304
 2305    // Move down. The editor cursor scrolls down to track the newest cursor.
 2306    cx.update_editor(|editor, window, cx| {
 2307        editor.move_down(&Default::default(), window, cx);
 2308    });
 2309    cx.update_editor(|editor, window, cx| {
 2310        assert_eq!(
 2311            editor.snapshot(window, cx).scroll_position(),
 2312            gpui::Point::new(0., 4.0)
 2313        );
 2314    });
 2315
 2316    // Add a cursor above the visible area. Since both cursors fit on screen,
 2317    // the editor scrolls to show both.
 2318    cx.update_editor(|editor, window, cx| {
 2319        editor.change_selections(Default::default(), window, cx, |selections| {
 2320            selections.select_ranges([
 2321                Point::new(1, 0)..Point::new(1, 0),
 2322                Point::new(6, 0)..Point::new(6, 0),
 2323            ]);
 2324        })
 2325    });
 2326    cx.update_editor(|editor, window, cx| {
 2327        assert_eq!(
 2328            editor.snapshot(window, cx).scroll_position(),
 2329            gpui::Point::new(0., 1.0)
 2330        );
 2331    });
 2332}
 2333
 2334#[gpui::test]
 2335async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2336    init_test(cx, |_| {});
 2337    let mut cx = EditorTestContext::new(cx).await;
 2338
 2339    let line_height = cx.editor(|editor, window, _cx| {
 2340        editor
 2341            .style()
 2342            .unwrap()
 2343            .text
 2344            .line_height_in_pixels(window.rem_size())
 2345    });
 2346    let window = cx.window;
 2347    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2348    cx.set_state(
 2349        &r#"
 2350        ˇone
 2351        two
 2352        threeˇ
 2353        four
 2354        five
 2355        six
 2356        seven
 2357        eight
 2358        nine
 2359        ten
 2360        "#
 2361        .unindent(),
 2362    );
 2363
 2364    cx.update_editor(|editor, window, cx| {
 2365        editor.move_page_down(&MovePageDown::default(), window, cx)
 2366    });
 2367    cx.assert_editor_state(
 2368        &r#"
 2369        one
 2370        two
 2371        three
 2372        ˇfour
 2373        five
 2374        sixˇ
 2375        seven
 2376        eight
 2377        nine
 2378        ten
 2379        "#
 2380        .unindent(),
 2381    );
 2382
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.move_page_down(&MovePageDown::default(), window, cx)
 2385    });
 2386    cx.assert_editor_state(
 2387        &r#"
 2388        one
 2389        two
 2390        three
 2391        four
 2392        five
 2393        six
 2394        ˇseven
 2395        eight
 2396        nineˇ
 2397        ten
 2398        "#
 2399        .unindent(),
 2400    );
 2401
 2402    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2403    cx.assert_editor_state(
 2404        &r#"
 2405        one
 2406        two
 2407        three
 2408        ˇfour
 2409        five
 2410        sixˇ
 2411        seven
 2412        eight
 2413        nine
 2414        ten
 2415        "#
 2416        .unindent(),
 2417    );
 2418
 2419    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2420    cx.assert_editor_state(
 2421        &r#"
 2422        ˇone
 2423        two
 2424        threeˇ
 2425        four
 2426        five
 2427        six
 2428        seven
 2429        eight
 2430        nine
 2431        ten
 2432        "#
 2433        .unindent(),
 2434    );
 2435
 2436    // Test select collapsing
 2437    cx.update_editor(|editor, window, cx| {
 2438        editor.move_page_down(&MovePageDown::default(), window, cx);
 2439        editor.move_page_down(&MovePageDown::default(), window, cx);
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441    });
 2442    cx.assert_editor_state(
 2443        &r#"
 2444        one
 2445        two
 2446        three
 2447        four
 2448        five
 2449        six
 2450        seven
 2451        eight
 2452        nine
 2453        ˇten
 2454        ˇ"#
 2455        .unindent(),
 2456    );
 2457}
 2458
 2459#[gpui::test]
 2460async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2461    init_test(cx, |_| {});
 2462    let mut cx = EditorTestContext::new(cx).await;
 2463    cx.set_state("one «two threeˇ» four");
 2464    cx.update_editor(|editor, window, cx| {
 2465        editor.delete_to_beginning_of_line(
 2466            &DeleteToBeginningOfLine {
 2467                stop_at_indent: false,
 2468            },
 2469            window,
 2470            cx,
 2471        );
 2472        assert_eq!(editor.text(cx), " four");
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479
 2480    let editor = cx.add_window(|window, cx| {
 2481        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2482        build_editor(buffer.clone(), window, cx)
 2483    });
 2484
 2485    _ = editor.update(cx, |editor, window, cx| {
 2486        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2487            s.select_display_ranges([
 2488                // an empty selection - the preceding word fragment is deleted
 2489                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2490                // characters selected - they are deleted
 2491                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2492            ])
 2493        });
 2494        editor.delete_to_previous_word_start(
 2495            &DeleteToPreviousWordStart {
 2496                ignore_newlines: false,
 2497            },
 2498            window,
 2499            cx,
 2500        );
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2502    });
 2503
 2504    _ = editor.update(cx, |editor, window, cx| {
 2505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2506            s.select_display_ranges([
 2507                // an empty selection - the following word fragment is deleted
 2508                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2509                // characters selected - they are deleted
 2510                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2511            ])
 2512        });
 2513        editor.delete_to_next_word_end(
 2514            &DeleteToNextWordEnd {
 2515                ignore_newlines: false,
 2516            },
 2517            window,
 2518            cx,
 2519        );
 2520        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2521    });
 2522}
 2523
 2524#[gpui::test]
 2525fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2526    init_test(cx, |_| {});
 2527
 2528    let editor = cx.add_window(|window, cx| {
 2529        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2530        build_editor(buffer.clone(), window, cx)
 2531    });
 2532    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2533        ignore_newlines: false,
 2534    };
 2535    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2536        ignore_newlines: true,
 2537    };
 2538
 2539    _ = editor.update(cx, |editor, window, cx| {
 2540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2541            s.select_display_ranges([
 2542                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2543            ])
 2544        });
 2545        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2547        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2549        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2550        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2551        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2553        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2555        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568    let del_to_next_word_end = DeleteToNextWordEnd {
 2569        ignore_newlines: false,
 2570    };
 2571    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2572        ignore_newlines: true,
 2573    };
 2574
 2575    _ = editor.update(cx, |editor, window, cx| {
 2576        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2577            s.select_display_ranges([
 2578                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2579            ])
 2580        });
 2581        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2582        assert_eq!(
 2583            editor.buffer.read(cx).read(cx).text(),
 2584            "one\n   two\nthree\n   four"
 2585        );
 2586        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2587        assert_eq!(
 2588            editor.buffer.read(cx).read(cx).text(),
 2589            "\n   two\nthree\n   four"
 2590        );
 2591        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2592        assert_eq!(
 2593            editor.buffer.read(cx).read(cx).text(),
 2594            "two\nthree\n   four"
 2595        );
 2596        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2597        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2598        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2599        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2600        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2601        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2602    });
 2603}
 2604
 2605#[gpui::test]
 2606fn test_newline(cx: &mut TestAppContext) {
 2607    init_test(cx, |_| {});
 2608
 2609    let editor = cx.add_window(|window, cx| {
 2610        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2611        build_editor(buffer.clone(), window, cx)
 2612    });
 2613
 2614    _ = editor.update(cx, |editor, window, cx| {
 2615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2616            s.select_display_ranges([
 2617                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2618                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2619                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2620            ])
 2621        });
 2622
 2623        editor.newline(&Newline, window, cx);
 2624        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2625    });
 2626}
 2627
 2628#[gpui::test]
 2629fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2630    init_test(cx, |_| {});
 2631
 2632    let editor = cx.add_window(|window, cx| {
 2633        let buffer = MultiBuffer::build_simple(
 2634            "
 2635                a
 2636                b(
 2637                    X
 2638                )
 2639                c(
 2640                    X
 2641                )
 2642            "
 2643            .unindent()
 2644            .as_str(),
 2645            cx,
 2646        );
 2647        let mut editor = build_editor(buffer.clone(), window, cx);
 2648        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2649            s.select_ranges([
 2650                Point::new(2, 4)..Point::new(2, 5),
 2651                Point::new(5, 4)..Point::new(5, 5),
 2652            ])
 2653        });
 2654        editor
 2655    });
 2656
 2657    _ = editor.update(cx, |editor, window, cx| {
 2658        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2659        editor.buffer.update(cx, |buffer, cx| {
 2660            buffer.edit(
 2661                [
 2662                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2663                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2664                ],
 2665                None,
 2666                cx,
 2667            );
 2668            assert_eq!(
 2669                buffer.read(cx).text(),
 2670                "
 2671                    a
 2672                    b()
 2673                    c()
 2674                "
 2675                .unindent()
 2676            );
 2677        });
 2678        assert_eq!(
 2679            editor.selections.ranges(cx),
 2680            &[
 2681                Point::new(1, 2)..Point::new(1, 2),
 2682                Point::new(2, 2)..Point::new(2, 2),
 2683            ],
 2684        );
 2685
 2686        editor.newline(&Newline, window, cx);
 2687        assert_eq!(
 2688            editor.text(cx),
 2689            "
 2690                a
 2691                b(
 2692                )
 2693                c(
 2694                )
 2695            "
 2696            .unindent()
 2697        );
 2698
 2699        // The selections are moved after the inserted newlines
 2700        assert_eq!(
 2701            editor.selections.ranges(cx),
 2702            &[
 2703                Point::new(2, 0)..Point::new(2, 0),
 2704                Point::new(4, 0)..Point::new(4, 0),
 2705            ],
 2706        );
 2707    });
 2708}
 2709
 2710#[gpui::test]
 2711async fn test_newline_above(cx: &mut TestAppContext) {
 2712    init_test(cx, |settings| {
 2713        settings.defaults.tab_size = NonZeroU32::new(4)
 2714    });
 2715
 2716    let language = Arc::new(
 2717        Language::new(
 2718            LanguageConfig::default(),
 2719            Some(tree_sitter_rust::LANGUAGE.into()),
 2720        )
 2721        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2722        .unwrap(),
 2723    );
 2724
 2725    let mut cx = EditorTestContext::new(cx).await;
 2726    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2727    cx.set_state(indoc! {"
 2728        const a: ˇA = (
 2729 2730                «const_functionˇ»(ˇ),
 2731                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2732 2733        ˇ);ˇ
 2734    "});
 2735
 2736    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2737    cx.assert_editor_state(indoc! {"
 2738        ˇ
 2739        const a: A = (
 2740            ˇ
 2741            (
 2742                ˇ
 2743                ˇ
 2744                const_function(),
 2745                ˇ
 2746                ˇ
 2747                ˇ
 2748                ˇ
 2749                something_else,
 2750                ˇ
 2751            )
 2752            ˇ
 2753            ˇ
 2754        );
 2755    "});
 2756}
 2757
 2758#[gpui::test]
 2759async fn test_newline_below(cx: &mut TestAppContext) {
 2760    init_test(cx, |settings| {
 2761        settings.defaults.tab_size = NonZeroU32::new(4)
 2762    });
 2763
 2764    let language = Arc::new(
 2765        Language::new(
 2766            LanguageConfig::default(),
 2767            Some(tree_sitter_rust::LANGUAGE.into()),
 2768        )
 2769        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2770        .unwrap(),
 2771    );
 2772
 2773    let mut cx = EditorTestContext::new(cx).await;
 2774    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2775    cx.set_state(indoc! {"
 2776        const a: ˇA = (
 2777 2778                «const_functionˇ»(ˇ),
 2779                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2780 2781        ˇ);ˇ
 2782    "});
 2783
 2784    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2785    cx.assert_editor_state(indoc! {"
 2786        const a: A = (
 2787            ˇ
 2788            (
 2789                ˇ
 2790                const_function(),
 2791                ˇ
 2792                ˇ
 2793                something_else,
 2794                ˇ
 2795                ˇ
 2796                ˇ
 2797                ˇ
 2798            )
 2799            ˇ
 2800        );
 2801        ˇ
 2802        ˇ
 2803    "});
 2804}
 2805
 2806#[gpui::test]
 2807async fn test_newline_comments(cx: &mut TestAppContext) {
 2808    init_test(cx, |settings| {
 2809        settings.defaults.tab_size = NonZeroU32::new(4)
 2810    });
 2811
 2812    let language = Arc::new(Language::new(
 2813        LanguageConfig {
 2814            line_comments: vec!["// ".into()],
 2815            ..LanguageConfig::default()
 2816        },
 2817        None,
 2818    ));
 2819    {
 2820        let mut cx = EditorTestContext::new(cx).await;
 2821        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2822        cx.set_state(indoc! {"
 2823        // Fooˇ
 2824    "});
 2825
 2826        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827        cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        // ˇ
 2830    "});
 2831        // Ensure that we add comment prefix when existing line contains space
 2832        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2833        cx.assert_editor_state(
 2834            indoc! {"
 2835        // Foo
 2836        //s
 2837        // ˇ
 2838    "}
 2839            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2840            .as_str(),
 2841        );
 2842        // Ensure that we add comment prefix when existing line does not contain space
 2843        cx.set_state(indoc! {"
 2844        // Foo
 2845        //ˇ
 2846    "});
 2847        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2848        cx.assert_editor_state(indoc! {"
 2849        // Foo
 2850        //
 2851        // ˇ
 2852    "});
 2853        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2854        cx.set_state(indoc! {"
 2855        ˇ// Foo
 2856    "});
 2857        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2858        cx.assert_editor_state(indoc! {"
 2859
 2860        ˇ// Foo
 2861    "});
 2862    }
 2863    // Ensure that comment continuations can be disabled.
 2864    update_test_language_settings(cx, |settings| {
 2865        settings.defaults.extend_comment_on_newline = Some(false);
 2866    });
 2867    let mut cx = EditorTestContext::new(cx).await;
 2868    cx.set_state(indoc! {"
 2869        // Fooˇ
 2870    "});
 2871    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2872    cx.assert_editor_state(indoc! {"
 2873        // Foo
 2874        ˇ
 2875    "});
 2876}
 2877
 2878#[gpui::test]
 2879async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2880    init_test(cx, |settings| {
 2881        settings.defaults.tab_size = NonZeroU32::new(4)
 2882    });
 2883
 2884    let language = Arc::new(Language::new(
 2885        LanguageConfig {
 2886            line_comments: vec!["// ".into(), "/// ".into()],
 2887            ..LanguageConfig::default()
 2888        },
 2889        None,
 2890    ));
 2891    {
 2892        let mut cx = EditorTestContext::new(cx).await;
 2893        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2894        cx.set_state(indoc! {"
 2895        //ˇ
 2896    "});
 2897        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2898        cx.assert_editor_state(indoc! {"
 2899        //
 2900        // ˇ
 2901    "});
 2902
 2903        cx.set_state(indoc! {"
 2904        ///ˇ
 2905    "});
 2906        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2907        cx.assert_editor_state(indoc! {"
 2908        ///
 2909        /// ˇ
 2910    "});
 2911    }
 2912}
 2913
 2914#[gpui::test]
 2915async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2916    init_test(cx, |settings| {
 2917        settings.defaults.tab_size = NonZeroU32::new(4)
 2918    });
 2919
 2920    let language = Arc::new(
 2921        Language::new(
 2922            LanguageConfig {
 2923                documentation_comment: Some(language::BlockCommentConfig {
 2924                    start: "/**".into(),
 2925                    end: "*/".into(),
 2926                    prefix: "* ".into(),
 2927                    tab_size: 1,
 2928                }),
 2929
 2930                ..LanguageConfig::default()
 2931            },
 2932            Some(tree_sitter_rust::LANGUAGE.into()),
 2933        )
 2934        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2935        .unwrap(),
 2936    );
 2937
 2938    {
 2939        let mut cx = EditorTestContext::new(cx).await;
 2940        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2941        cx.set_state(indoc! {"
 2942        /**ˇ
 2943    "});
 2944
 2945        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2946        cx.assert_editor_state(indoc! {"
 2947        /**
 2948         * ˇ
 2949    "});
 2950        // Ensure that if cursor is before the comment start,
 2951        // we do not actually insert a comment prefix.
 2952        cx.set_state(indoc! {"
 2953        ˇ/**
 2954    "});
 2955        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2956        cx.assert_editor_state(indoc! {"
 2957
 2958        ˇ/**
 2959    "});
 2960        // Ensure that if cursor is between it doesn't add comment prefix.
 2961        cx.set_state(indoc! {"
 2962        /*ˇ*
 2963    "});
 2964        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2965        cx.assert_editor_state(indoc! {"
 2966        /*
 2967        ˇ*
 2968    "});
 2969        // Ensure that if suffix exists on same line after cursor it adds new line.
 2970        cx.set_state(indoc! {"
 2971        /**ˇ*/
 2972    "});
 2973        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2974        cx.assert_editor_state(indoc! {"
 2975        /**
 2976         * ˇ
 2977         */
 2978    "});
 2979        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2980        cx.set_state(indoc! {"
 2981        /**ˇ */
 2982    "});
 2983        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2984        cx.assert_editor_state(indoc! {"
 2985        /**
 2986         * ˇ
 2987         */
 2988    "});
 2989        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2990        cx.set_state(indoc! {"
 2991        /** ˇ*/
 2992    "});
 2993        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2994        cx.assert_editor_state(
 2995            indoc! {"
 2996        /**s
 2997         * ˇ
 2998         */
 2999    "}
 3000            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3001            .as_str(),
 3002        );
 3003        // Ensure that delimiter space is preserved when newline on already
 3004        // spaced delimiter.
 3005        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3006        cx.assert_editor_state(
 3007            indoc! {"
 3008        /**s
 3009         *s
 3010         * ˇ
 3011         */
 3012    "}
 3013            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3014            .as_str(),
 3015        );
 3016        // Ensure that delimiter space is preserved when space is not
 3017        // on existing delimiter.
 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        // Ensure that if suffix exists on same line after cursor it
 3031        // doesn't add extra new line if prefix is not on same line.
 3032        cx.set_state(indoc! {"
 3033        /**
 3034        ˇ*/
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /**
 3039
 3040        ˇ*/
 3041    "});
 3042        // Ensure that it detects suffix after existing prefix.
 3043        cx.set_state(indoc! {"
 3044        /**ˇ/
 3045    "});
 3046        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3047        cx.assert_editor_state(indoc! {"
 3048        /**
 3049        ˇ/
 3050    "});
 3051        // Ensure that if suffix exists on same line before
 3052        // cursor it does not add comment prefix.
 3053        cx.set_state(indoc! {"
 3054        /** */ˇ
 3055    "});
 3056        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3057        cx.assert_editor_state(indoc! {"
 3058        /** */
 3059        ˇ
 3060    "});
 3061        // Ensure that if suffix exists on same line before
 3062        // cursor it does not add comment prefix.
 3063        cx.set_state(indoc! {"
 3064        /**
 3065         *
 3066         */ˇ
 3067    "});
 3068        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3069        cx.assert_editor_state(indoc! {"
 3070        /**
 3071         *
 3072         */
 3073         ˇ
 3074    "});
 3075
 3076        // Ensure that inline comment followed by code
 3077        // doesn't add comment prefix on newline
 3078        cx.set_state(indoc! {"
 3079        /** */ textˇ
 3080    "});
 3081        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3082        cx.assert_editor_state(indoc! {"
 3083        /** */ text
 3084        ˇ
 3085    "});
 3086
 3087        // Ensure that text after comment end tag
 3088        // doesn't add comment prefix on newline
 3089        cx.set_state(indoc! {"
 3090        /**
 3091         *
 3092         */ˇtext
 3093    "});
 3094        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3095        cx.assert_editor_state(indoc! {"
 3096        /**
 3097         *
 3098         */
 3099         ˇtext
 3100    "});
 3101
 3102        // Ensure if not comment block it doesn't
 3103        // add comment prefix on newline
 3104        cx.set_state(indoc! {"
 3105        * textˇ
 3106    "});
 3107        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3108        cx.assert_editor_state(indoc! {"
 3109        * text
 3110        ˇ
 3111    "});
 3112    }
 3113    // Ensure that comment continuations can be disabled.
 3114    update_test_language_settings(cx, |settings| {
 3115        settings.defaults.extend_comment_on_newline = Some(false);
 3116    });
 3117    let mut cx = EditorTestContext::new(cx).await;
 3118    cx.set_state(indoc! {"
 3119        /**ˇ
 3120    "});
 3121    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3122    cx.assert_editor_state(indoc! {"
 3123        /**
 3124        ˇ
 3125    "});
 3126}
 3127
 3128#[gpui::test]
 3129async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3130    init_test(cx, |settings| {
 3131        settings.defaults.tab_size = NonZeroU32::new(4)
 3132    });
 3133
 3134    let lua_language = Arc::new(Language::new(
 3135        LanguageConfig {
 3136            line_comments: vec!["--".into()],
 3137            block_comment: Some(language::BlockCommentConfig {
 3138                start: "--[[".into(),
 3139                prefix: "".into(),
 3140                end: "]]".into(),
 3141                tab_size: 0,
 3142            }),
 3143            ..LanguageConfig::default()
 3144        },
 3145        None,
 3146    ));
 3147
 3148    let mut cx = EditorTestContext::new(cx).await;
 3149    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3150
 3151    // Line with line comment should extend
 3152    cx.set_state(indoc! {"
 3153        --ˇ
 3154    "});
 3155    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3156    cx.assert_editor_state(indoc! {"
 3157        --
 3158        --ˇ
 3159    "});
 3160
 3161    // Line with block comment that matches line comment should not extend
 3162    cx.set_state(indoc! {"
 3163        --[[ˇ
 3164    "});
 3165    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3166    cx.assert_editor_state(indoc! {"
 3167        --[[
 3168        ˇ
 3169    "});
 3170}
 3171
 3172#[gpui::test]
 3173fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3174    init_test(cx, |_| {});
 3175
 3176    let editor = cx.add_window(|window, cx| {
 3177        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3178        let mut editor = build_editor(buffer.clone(), window, cx);
 3179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3180            s.select_ranges([3..4, 11..12, 19..20])
 3181        });
 3182        editor
 3183    });
 3184
 3185    _ = editor.update(cx, |editor, window, cx| {
 3186        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3187        editor.buffer.update(cx, |buffer, cx| {
 3188            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3189            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3190        });
 3191        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3192
 3193        editor.insert("Z", window, cx);
 3194        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3195
 3196        // The selections are moved after the inserted characters
 3197        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3198    });
 3199}
 3200
 3201#[gpui::test]
 3202async fn test_tab(cx: &mut TestAppContext) {
 3203    init_test(cx, |settings| {
 3204        settings.defaults.tab_size = NonZeroU32::new(3)
 3205    });
 3206
 3207    let mut cx = EditorTestContext::new(cx).await;
 3208    cx.set_state(indoc! {"
 3209        ˇabˇc
 3210        ˇ🏀ˇ🏀ˇefg
 3211 3212    "});
 3213    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3214    cx.assert_editor_state(indoc! {"
 3215           ˇab ˇc
 3216           ˇ🏀  ˇ🏀  ˇefg
 3217        d  ˇ
 3218    "});
 3219
 3220    cx.set_state(indoc! {"
 3221        a
 3222        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3223    "});
 3224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3225    cx.assert_editor_state(indoc! {"
 3226        a
 3227           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3228    "});
 3229}
 3230
 3231#[gpui::test]
 3232async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3233    init_test(cx, |_| {});
 3234
 3235    let mut cx = EditorTestContext::new(cx).await;
 3236    let language = Arc::new(
 3237        Language::new(
 3238            LanguageConfig::default(),
 3239            Some(tree_sitter_rust::LANGUAGE.into()),
 3240        )
 3241        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3242        .unwrap(),
 3243    );
 3244    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3245
 3246    // test when all cursors are not at suggested indent
 3247    // then simply move to their suggested indent location
 3248    cx.set_state(indoc! {"
 3249        const a: B = (
 3250            c(
 3251        ˇ
 3252        ˇ    )
 3253        );
 3254    "});
 3255    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3256    cx.assert_editor_state(indoc! {"
 3257        const a: B = (
 3258            c(
 3259                ˇ
 3260            ˇ)
 3261        );
 3262    "});
 3263
 3264    // test cursor already at suggested indent not moving when
 3265    // other cursors are yet to reach their suggested indents
 3266    cx.set_state(indoc! {"
 3267        ˇ
 3268        const a: B = (
 3269            c(
 3270                d(
 3271        ˇ
 3272                )
 3273        ˇ
 3274        ˇ    )
 3275        );
 3276    "});
 3277    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3278    cx.assert_editor_state(indoc! {"
 3279        ˇ
 3280        const a: B = (
 3281            c(
 3282                d(
 3283                    ˇ
 3284                )
 3285                ˇ
 3286            ˇ)
 3287        );
 3288    "});
 3289    // test when all cursors are at suggested indent then tab is inserted
 3290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3291    cx.assert_editor_state(indoc! {"
 3292            ˇ
 3293        const a: B = (
 3294            c(
 3295                d(
 3296                        ˇ
 3297                )
 3298                    ˇ
 3299                ˇ)
 3300        );
 3301    "});
 3302
 3303    // test when current indent is less than suggested indent,
 3304    // we adjust line to match suggested indent and move cursor to it
 3305    //
 3306    // when no other cursor is at word boundary, all of them should move
 3307    cx.set_state(indoc! {"
 3308        const a: B = (
 3309            c(
 3310                d(
 3311        ˇ
 3312        ˇ   )
 3313        ˇ   )
 3314        );
 3315    "});
 3316    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3317    cx.assert_editor_state(indoc! {"
 3318        const a: B = (
 3319            c(
 3320                d(
 3321                    ˇ
 3322                ˇ)
 3323            ˇ)
 3324        );
 3325    "});
 3326
 3327    // test when current indent is less than suggested indent,
 3328    // we adjust line to match suggested indent and move cursor to it
 3329    //
 3330    // when some other cursor is at word boundary, it should not move
 3331    cx.set_state(indoc! {"
 3332        const a: B = (
 3333            c(
 3334                d(
 3335        ˇ
 3336        ˇ   )
 3337           ˇ)
 3338        );
 3339    "});
 3340    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3341    cx.assert_editor_state(indoc! {"
 3342        const a: B = (
 3343            c(
 3344                d(
 3345                    ˇ
 3346                ˇ)
 3347            ˇ)
 3348        );
 3349    "});
 3350
 3351    // test when current indent is more than suggested indent,
 3352    // we just move cursor to current indent instead of suggested indent
 3353    //
 3354    // when no other cursor is at word boundary, all of them should move
 3355    cx.set_state(indoc! {"
 3356        const a: B = (
 3357            c(
 3358                d(
 3359        ˇ
 3360        ˇ                )
 3361        ˇ   )
 3362        );
 3363    "});
 3364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3365    cx.assert_editor_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368                d(
 3369                    ˇ
 3370                        ˇ)
 3371            ˇ)
 3372        );
 3373    "});
 3374    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3375    cx.assert_editor_state(indoc! {"
 3376        const a: B = (
 3377            c(
 3378                d(
 3379                        ˇ
 3380                            ˇ)
 3381                ˇ)
 3382        );
 3383    "});
 3384
 3385    // test when current indent is more than suggested indent,
 3386    // we just move cursor to current indent instead of suggested indent
 3387    //
 3388    // when some other cursor is at word boundary, it doesn't move
 3389    cx.set_state(indoc! {"
 3390        const a: B = (
 3391            c(
 3392                d(
 3393        ˇ
 3394        ˇ                )
 3395            ˇ)
 3396        );
 3397    "});
 3398    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3399    cx.assert_editor_state(indoc! {"
 3400        const a: B = (
 3401            c(
 3402                d(
 3403                    ˇ
 3404                        ˇ)
 3405            ˇ)
 3406        );
 3407    "});
 3408
 3409    // handle auto-indent when there are multiple cursors on the same line
 3410    cx.set_state(indoc! {"
 3411        const a: B = (
 3412            c(
 3413        ˇ    ˇ
 3414        ˇ    )
 3415        );
 3416    "});
 3417    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        const a: B = (
 3420            c(
 3421                ˇ
 3422            ˇ)
 3423        );
 3424    "});
 3425}
 3426
 3427#[gpui::test]
 3428async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3429    init_test(cx, |settings| {
 3430        settings.defaults.tab_size = NonZeroU32::new(3)
 3431    });
 3432
 3433    let mut cx = EditorTestContext::new(cx).await;
 3434    cx.set_state(indoc! {"
 3435         ˇ
 3436        \t ˇ
 3437        \t  ˇ
 3438        \t   ˇ
 3439         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3440    "});
 3441
 3442    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3443    cx.assert_editor_state(indoc! {"
 3444           ˇ
 3445        \t   ˇ
 3446        \t   ˇ
 3447        \t      ˇ
 3448         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3449    "});
 3450}
 3451
 3452#[gpui::test]
 3453async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3454    init_test(cx, |settings| {
 3455        settings.defaults.tab_size = NonZeroU32::new(4)
 3456    });
 3457
 3458    let language = Arc::new(
 3459        Language::new(
 3460            LanguageConfig::default(),
 3461            Some(tree_sitter_rust::LANGUAGE.into()),
 3462        )
 3463        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3464        .unwrap(),
 3465    );
 3466
 3467    let mut cx = EditorTestContext::new(cx).await;
 3468    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3469    cx.set_state(indoc! {"
 3470        fn a() {
 3471            if b {
 3472        \t ˇc
 3473            }
 3474        }
 3475    "});
 3476
 3477    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        fn a() {
 3480            if b {
 3481                ˇc
 3482            }
 3483        }
 3484    "});
 3485}
 3486
 3487#[gpui::test]
 3488async fn test_indent_outdent(cx: &mut TestAppContext) {
 3489    init_test(cx, |settings| {
 3490        settings.defaults.tab_size = NonZeroU32::new(4);
 3491    });
 3492
 3493    let mut cx = EditorTestContext::new(cx).await;
 3494
 3495    cx.set_state(indoc! {"
 3496          «oneˇ» «twoˇ»
 3497        three
 3498         four
 3499    "});
 3500    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3501    cx.assert_editor_state(indoc! {"
 3502            «oneˇ» «twoˇ»
 3503        three
 3504         four
 3505    "});
 3506
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        «oneˇ» «twoˇ»
 3510        three
 3511         four
 3512    "});
 3513
 3514    // select across line ending
 3515    cx.set_state(indoc! {"
 3516        one two
 3517        t«hree
 3518        ˇ» four
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        one two
 3523            t«hree
 3524        ˇ» four
 3525    "});
 3526
 3527    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3528    cx.assert_editor_state(indoc! {"
 3529        one two
 3530        t«hree
 3531        ˇ» four
 3532    "});
 3533
 3534    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3535    cx.set_state(indoc! {"
 3536        one two
 3537        ˇthree
 3538            four
 3539    "});
 3540    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3541    cx.assert_editor_state(indoc! {"
 3542        one two
 3543            ˇthree
 3544            four
 3545    "});
 3546
 3547    cx.set_state(indoc! {"
 3548        one two
 3549        ˇ    three
 3550            four
 3551    "});
 3552    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3553    cx.assert_editor_state(indoc! {"
 3554        one two
 3555        ˇthree
 3556            four
 3557    "});
 3558}
 3559
 3560#[gpui::test]
 3561async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3562    // This is a regression test for issue #33761
 3563    init_test(cx, |_| {});
 3564
 3565    let mut cx = EditorTestContext::new(cx).await;
 3566    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3568
 3569    cx.set_state(
 3570        r#"ˇ#     ingress:
 3571ˇ#         api:
 3572ˇ#             enabled: false
 3573ˇ#             pathType: Prefix
 3574ˇ#           console:
 3575ˇ#               enabled: false
 3576ˇ#               pathType: Prefix
 3577"#,
 3578    );
 3579
 3580    // Press tab to indent all lines
 3581    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3582
 3583    cx.assert_editor_state(
 3584        r#"    ˇ#     ingress:
 3585    ˇ#         api:
 3586    ˇ#             enabled: false
 3587    ˇ#             pathType: Prefix
 3588    ˇ#           console:
 3589    ˇ#               enabled: false
 3590    ˇ#               pathType: Prefix
 3591"#,
 3592    );
 3593}
 3594
 3595#[gpui::test]
 3596async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3597    // This is a test to make sure our fix for issue #33761 didn't break anything
 3598    init_test(cx, |_| {});
 3599
 3600    let mut cx = EditorTestContext::new(cx).await;
 3601    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3602    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3603
 3604    cx.set_state(
 3605        r#"ˇingress:
 3606ˇ  api:
 3607ˇ    enabled: false
 3608ˇ    pathType: Prefix
 3609"#,
 3610    );
 3611
 3612    // Press tab to indent all lines
 3613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3614
 3615    cx.assert_editor_state(
 3616        r#"ˇingress:
 3617    ˇapi:
 3618        ˇenabled: false
 3619        ˇpathType: Prefix
 3620"#,
 3621    );
 3622}
 3623
 3624#[gpui::test]
 3625async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3626    init_test(cx, |settings| {
 3627        settings.defaults.hard_tabs = Some(true);
 3628    });
 3629
 3630    let mut cx = EditorTestContext::new(cx).await;
 3631
 3632    // select two ranges on one line
 3633    cx.set_state(indoc! {"
 3634        «oneˇ» «twoˇ»
 3635        three
 3636        four
 3637    "});
 3638    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3639    cx.assert_editor_state(indoc! {"
 3640        \t«oneˇ» «twoˇ»
 3641        three
 3642        four
 3643    "});
 3644    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3645    cx.assert_editor_state(indoc! {"
 3646        \t\t«oneˇ» «twoˇ»
 3647        three
 3648        four
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        \t«oneˇ» «twoˇ»
 3653        three
 3654        four
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        «oneˇ» «twoˇ»
 3659        three
 3660        four
 3661    "});
 3662
 3663    // select across a line ending
 3664    cx.set_state(indoc! {"
 3665        one two
 3666        t«hree
 3667        ˇ»four
 3668    "});
 3669    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3670    cx.assert_editor_state(indoc! {"
 3671        one two
 3672        \tt«hree
 3673        ˇ»four
 3674    "});
 3675    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3676    cx.assert_editor_state(indoc! {"
 3677        one two
 3678        \t\tt«hree
 3679        ˇ»four
 3680    "});
 3681    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3682    cx.assert_editor_state(indoc! {"
 3683        one two
 3684        \tt«hree
 3685        ˇ»four
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        one two
 3690        t«hree
 3691        ˇ»four
 3692    "});
 3693
 3694    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3695    cx.set_state(indoc! {"
 3696        one two
 3697        ˇthree
 3698        four
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        one two
 3703        ˇthree
 3704        four
 3705    "});
 3706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3707    cx.assert_editor_state(indoc! {"
 3708        one two
 3709        \tˇthree
 3710        four
 3711    "});
 3712    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3713    cx.assert_editor_state(indoc! {"
 3714        one two
 3715        ˇthree
 3716        four
 3717    "});
 3718}
 3719
 3720#[gpui::test]
 3721fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3722    init_test(cx, |settings| {
 3723        settings.languages.0.extend([
 3724            (
 3725                "TOML".into(),
 3726                LanguageSettingsContent {
 3727                    tab_size: NonZeroU32::new(2),
 3728                    ..Default::default()
 3729                },
 3730            ),
 3731            (
 3732                "Rust".into(),
 3733                LanguageSettingsContent {
 3734                    tab_size: NonZeroU32::new(4),
 3735                    ..Default::default()
 3736                },
 3737            ),
 3738        ]);
 3739    });
 3740
 3741    let toml_language = Arc::new(Language::new(
 3742        LanguageConfig {
 3743            name: "TOML".into(),
 3744            ..Default::default()
 3745        },
 3746        None,
 3747    ));
 3748    let rust_language = Arc::new(Language::new(
 3749        LanguageConfig {
 3750            name: "Rust".into(),
 3751            ..Default::default()
 3752        },
 3753        None,
 3754    ));
 3755
 3756    let toml_buffer =
 3757        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3758    let rust_buffer =
 3759        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3760    let multibuffer = cx.new(|cx| {
 3761        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3762        multibuffer.push_excerpts(
 3763            toml_buffer.clone(),
 3764            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3765            cx,
 3766        );
 3767        multibuffer.push_excerpts(
 3768            rust_buffer.clone(),
 3769            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3770            cx,
 3771        );
 3772        multibuffer
 3773    });
 3774
 3775    cx.add_window(|window, cx| {
 3776        let mut editor = build_editor(multibuffer, window, cx);
 3777
 3778        assert_eq!(
 3779            editor.text(cx),
 3780            indoc! {"
 3781                a = 1
 3782                b = 2
 3783
 3784                const c: usize = 3;
 3785            "}
 3786        );
 3787
 3788        select_ranges(
 3789            &mut editor,
 3790            indoc! {"
 3791                «aˇ» = 1
 3792                b = 2
 3793
 3794                «const c:ˇ» usize = 3;
 3795            "},
 3796            window,
 3797            cx,
 3798        );
 3799
 3800        editor.tab(&Tab, window, cx);
 3801        assert_text_with_selections(
 3802            &mut editor,
 3803            indoc! {"
 3804                  «aˇ» = 1
 3805                b = 2
 3806
 3807                    «const c:ˇ» usize = 3;
 3808            "},
 3809            cx,
 3810        );
 3811        editor.backtab(&Backtab, window, cx);
 3812        assert_text_with_selections(
 3813            &mut editor,
 3814            indoc! {"
 3815                «aˇ» = 1
 3816                b = 2
 3817
 3818                «const c:ˇ» usize = 3;
 3819            "},
 3820            cx,
 3821        );
 3822
 3823        editor
 3824    });
 3825}
 3826
 3827#[gpui::test]
 3828async fn test_backspace(cx: &mut TestAppContext) {
 3829    init_test(cx, |_| {});
 3830
 3831    let mut cx = EditorTestContext::new(cx).await;
 3832
 3833    // Basic backspace
 3834    cx.set_state(indoc! {"
 3835        onˇe two three
 3836        fou«rˇ» five six
 3837        seven «ˇeight nine
 3838        »ten
 3839    "});
 3840    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3841    cx.assert_editor_state(indoc! {"
 3842        oˇe two three
 3843        fouˇ five six
 3844        seven ˇten
 3845    "});
 3846
 3847    // Test backspace inside and around indents
 3848    cx.set_state(indoc! {"
 3849        zero
 3850            ˇone
 3851                ˇtwo
 3852            ˇ ˇ ˇ  three
 3853        ˇ  ˇ  four
 3854    "});
 3855    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3856    cx.assert_editor_state(indoc! {"
 3857        zero
 3858        ˇone
 3859            ˇtwo
 3860        ˇ  threeˇ  four
 3861    "});
 3862}
 3863
 3864#[gpui::test]
 3865async fn test_delete(cx: &mut TestAppContext) {
 3866    init_test(cx, |_| {});
 3867
 3868    let mut cx = EditorTestContext::new(cx).await;
 3869    cx.set_state(indoc! {"
 3870        onˇe two three
 3871        fou«rˇ» five six
 3872        seven «ˇeight nine
 3873        »ten
 3874    "});
 3875    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3876    cx.assert_editor_state(indoc! {"
 3877        onˇ two three
 3878        fouˇ five six
 3879        seven ˇten
 3880    "});
 3881}
 3882
 3883#[gpui::test]
 3884fn test_delete_line(cx: &mut TestAppContext) {
 3885    init_test(cx, |_| {});
 3886
 3887    let editor = cx.add_window(|window, cx| {
 3888        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3889        build_editor(buffer, window, cx)
 3890    });
 3891    _ = editor.update(cx, |editor, window, cx| {
 3892        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3893            s.select_display_ranges([
 3894                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3895                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3896                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3897            ])
 3898        });
 3899        editor.delete_line(&DeleteLine, window, cx);
 3900        assert_eq!(editor.display_text(cx), "ghi");
 3901        assert_eq!(
 3902            editor.selections.display_ranges(cx),
 3903            vec![
 3904                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3905                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3906            ]
 3907        );
 3908    });
 3909
 3910    let editor = cx.add_window(|window, cx| {
 3911        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3912        build_editor(buffer, window, cx)
 3913    });
 3914    _ = editor.update(cx, |editor, window, cx| {
 3915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3916            s.select_display_ranges([
 3917                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3918            ])
 3919        });
 3920        editor.delete_line(&DeleteLine, window, cx);
 3921        assert_eq!(editor.display_text(cx), "ghi\n");
 3922        assert_eq!(
 3923            editor.selections.display_ranges(cx),
 3924            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3925        );
 3926    });
 3927}
 3928
 3929#[gpui::test]
 3930fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3931    init_test(cx, |_| {});
 3932
 3933    cx.add_window(|window, cx| {
 3934        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3935        let mut editor = build_editor(buffer.clone(), window, cx);
 3936        let buffer = buffer.read(cx).as_singleton().unwrap();
 3937
 3938        assert_eq!(
 3939            editor.selections.ranges::<Point>(cx),
 3940            &[Point::new(0, 0)..Point::new(0, 0)]
 3941        );
 3942
 3943        // When on single line, replace newline at end by space
 3944        editor.join_lines(&JoinLines, window, cx);
 3945        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3946        assert_eq!(
 3947            editor.selections.ranges::<Point>(cx),
 3948            &[Point::new(0, 3)..Point::new(0, 3)]
 3949        );
 3950
 3951        // When multiple lines are selected, remove newlines that are spanned by the selection
 3952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3953            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3954        });
 3955        editor.join_lines(&JoinLines, window, cx);
 3956        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3957        assert_eq!(
 3958            editor.selections.ranges::<Point>(cx),
 3959            &[Point::new(0, 11)..Point::new(0, 11)]
 3960        );
 3961
 3962        // Undo should be transactional
 3963        editor.undo(&Undo, window, cx);
 3964        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3965        assert_eq!(
 3966            editor.selections.ranges::<Point>(cx),
 3967            &[Point::new(0, 5)..Point::new(2, 2)]
 3968        );
 3969
 3970        // When joining an empty line don't insert a space
 3971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3972            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3973        });
 3974        editor.join_lines(&JoinLines, window, cx);
 3975        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3976        assert_eq!(
 3977            editor.selections.ranges::<Point>(cx),
 3978            [Point::new(2, 3)..Point::new(2, 3)]
 3979        );
 3980
 3981        // We can remove trailing newlines
 3982        editor.join_lines(&JoinLines, window, cx);
 3983        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3984        assert_eq!(
 3985            editor.selections.ranges::<Point>(cx),
 3986            [Point::new(2, 3)..Point::new(2, 3)]
 3987        );
 3988
 3989        // We don't blow up on the last line
 3990        editor.join_lines(&JoinLines, window, cx);
 3991        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3992        assert_eq!(
 3993            editor.selections.ranges::<Point>(cx),
 3994            [Point::new(2, 3)..Point::new(2, 3)]
 3995        );
 3996
 3997        // reset to test indentation
 3998        editor.buffer.update(cx, |buffer, cx| {
 3999            buffer.edit(
 4000                [
 4001                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4002                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4003                ],
 4004                None,
 4005                cx,
 4006            )
 4007        });
 4008
 4009        // We remove any leading spaces
 4010        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4012            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4013        });
 4014        editor.join_lines(&JoinLines, window, cx);
 4015        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4016
 4017        // We don't insert a space for a line containing only spaces
 4018        editor.join_lines(&JoinLines, window, cx);
 4019        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4020
 4021        // We ignore any leading tabs
 4022        editor.join_lines(&JoinLines, window, cx);
 4023        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4024
 4025        editor
 4026    });
 4027}
 4028
 4029#[gpui::test]
 4030fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4031    init_test(cx, |_| {});
 4032
 4033    cx.add_window(|window, cx| {
 4034        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4035        let mut editor = build_editor(buffer.clone(), window, cx);
 4036        let buffer = buffer.read(cx).as_singleton().unwrap();
 4037
 4038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4039            s.select_ranges([
 4040                Point::new(0, 2)..Point::new(1, 1),
 4041                Point::new(1, 2)..Point::new(1, 2),
 4042                Point::new(3, 1)..Point::new(3, 2),
 4043            ])
 4044        });
 4045
 4046        editor.join_lines(&JoinLines, window, cx);
 4047        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4048
 4049        assert_eq!(
 4050            editor.selections.ranges::<Point>(cx),
 4051            [
 4052                Point::new(0, 7)..Point::new(0, 7),
 4053                Point::new(1, 3)..Point::new(1, 3)
 4054            ]
 4055        );
 4056        editor
 4057    });
 4058}
 4059
 4060#[gpui::test]
 4061async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4062    init_test(cx, |_| {});
 4063
 4064    let mut cx = EditorTestContext::new(cx).await;
 4065
 4066    let diff_base = r#"
 4067        Line 0
 4068        Line 1
 4069        Line 2
 4070        Line 3
 4071        "#
 4072    .unindent();
 4073
 4074    cx.set_state(
 4075        &r#"
 4076        ˇLine 0
 4077        Line 1
 4078        Line 2
 4079        Line 3
 4080        "#
 4081        .unindent(),
 4082    );
 4083
 4084    cx.set_head_text(&diff_base);
 4085    executor.run_until_parked();
 4086
 4087    // Join lines
 4088    cx.update_editor(|editor, window, cx| {
 4089        editor.join_lines(&JoinLines, window, cx);
 4090    });
 4091    executor.run_until_parked();
 4092
 4093    cx.assert_editor_state(
 4094        &r#"
 4095        Line 0ˇ Line 1
 4096        Line 2
 4097        Line 3
 4098        "#
 4099        .unindent(),
 4100    );
 4101    // Join again
 4102    cx.update_editor(|editor, window, cx| {
 4103        editor.join_lines(&JoinLines, window, cx);
 4104    });
 4105    executor.run_until_parked();
 4106
 4107    cx.assert_editor_state(
 4108        &r#"
 4109        Line 0 Line 1ˇ Line 2
 4110        Line 3
 4111        "#
 4112        .unindent(),
 4113    );
 4114}
 4115
 4116#[gpui::test]
 4117async fn test_custom_newlines_cause_no_false_positive_diffs(
 4118    executor: BackgroundExecutor,
 4119    cx: &mut TestAppContext,
 4120) {
 4121    init_test(cx, |_| {});
 4122    let mut cx = EditorTestContext::new(cx).await;
 4123    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4124    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4125    executor.run_until_parked();
 4126
 4127    cx.update_editor(|editor, window, cx| {
 4128        let snapshot = editor.snapshot(window, cx);
 4129        assert_eq!(
 4130            snapshot
 4131                .buffer_snapshot
 4132                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4133                .collect::<Vec<_>>(),
 4134            Vec::new(),
 4135            "Should not have any diffs for files with custom newlines"
 4136        );
 4137    });
 4138}
 4139
 4140#[gpui::test]
 4141async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4142    init_test(cx, |_| {});
 4143
 4144    let mut cx = EditorTestContext::new(cx).await;
 4145
 4146    // Test sort_lines_case_insensitive()
 4147    cx.set_state(indoc! {"
 4148        «z
 4149        y
 4150        x
 4151        Z
 4152        Y
 4153        Xˇ»
 4154    "});
 4155    cx.update_editor(|e, window, cx| {
 4156        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4157    });
 4158    cx.assert_editor_state(indoc! {"
 4159        «x
 4160        X
 4161        y
 4162        Y
 4163        z
 4164        Zˇ»
 4165    "});
 4166
 4167    // Test sort_lines_by_length()
 4168    //
 4169    // Demonstrates:
 4170    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4171    // - sort is stable
 4172    cx.set_state(indoc! {"
 4173        «123
 4174        æ
 4175        12
 4176 4177        1
 4178        æˇ»
 4179    "});
 4180    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4181    cx.assert_editor_state(indoc! {"
 4182        «æ
 4183 4184        1
 4185        æ
 4186        12
 4187        123ˇ»
 4188    "});
 4189
 4190    // Test reverse_lines()
 4191    cx.set_state(indoc! {"
 4192        «5
 4193        4
 4194        3
 4195        2
 4196        1ˇ»
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        «1
 4201        2
 4202        3
 4203        4
 4204        5ˇ»
 4205    "});
 4206
 4207    // Skip testing shuffle_line()
 4208
 4209    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4210    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4211
 4212    // Don't manipulate when cursor is on single line, but expand the selection
 4213    cx.set_state(indoc! {"
 4214        ddˇdd
 4215        ccc
 4216        bb
 4217        a
 4218    "});
 4219    cx.update_editor(|e, window, cx| {
 4220        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4221    });
 4222    cx.assert_editor_state(indoc! {"
 4223        «ddddˇ»
 4224        ccc
 4225        bb
 4226        a
 4227    "});
 4228
 4229    // Basic manipulate case
 4230    // Start selection moves to column 0
 4231    // End of selection shrinks to fit shorter line
 4232    cx.set_state(indoc! {"
 4233        dd«d
 4234        ccc
 4235        bb
 4236        aaaaaˇ»
 4237    "});
 4238    cx.update_editor(|e, window, cx| {
 4239        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4240    });
 4241    cx.assert_editor_state(indoc! {"
 4242        «aaaaa
 4243        bb
 4244        ccc
 4245        dddˇ»
 4246    "});
 4247
 4248    // Manipulate case with newlines
 4249    cx.set_state(indoc! {"
 4250        dd«d
 4251        ccc
 4252
 4253        bb
 4254        aaaaa
 4255
 4256        ˇ»
 4257    "});
 4258    cx.update_editor(|e, window, cx| {
 4259        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4260    });
 4261    cx.assert_editor_state(indoc! {"
 4262        «
 4263
 4264        aaaaa
 4265        bb
 4266        ccc
 4267        dddˇ»
 4268
 4269    "});
 4270
 4271    // Adding new line
 4272    cx.set_state(indoc! {"
 4273        aa«a
 4274        bbˇ»b
 4275    "});
 4276    cx.update_editor(|e, window, cx| {
 4277        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4278    });
 4279    cx.assert_editor_state(indoc! {"
 4280        «aaa
 4281        bbb
 4282        added_lineˇ»
 4283    "});
 4284
 4285    // Removing line
 4286    cx.set_state(indoc! {"
 4287        aa«a
 4288        bbbˇ»
 4289    "});
 4290    cx.update_editor(|e, window, cx| {
 4291        e.manipulate_immutable_lines(window, cx, |lines| {
 4292            lines.pop();
 4293        })
 4294    });
 4295    cx.assert_editor_state(indoc! {"
 4296        «aaaˇ»
 4297    "});
 4298
 4299    // Removing all lines
 4300    cx.set_state(indoc! {"
 4301        aa«a
 4302        bbbˇ»
 4303    "});
 4304    cx.update_editor(|e, window, cx| {
 4305        e.manipulate_immutable_lines(window, cx, |lines| {
 4306            lines.drain(..);
 4307        })
 4308    });
 4309    cx.assert_editor_state(indoc! {"
 4310        ˇ
 4311    "});
 4312}
 4313
 4314#[gpui::test]
 4315async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4316    init_test(cx, |_| {});
 4317
 4318    let mut cx = EditorTestContext::new(cx).await;
 4319
 4320    // Consider continuous selection as single selection
 4321    cx.set_state(indoc! {"
 4322        Aaa«aa
 4323        cˇ»c«c
 4324        bb
 4325        aaaˇ»aa
 4326    "});
 4327    cx.update_editor(|e, window, cx| {
 4328        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4329    });
 4330    cx.assert_editor_state(indoc! {"
 4331        «Aaaaa
 4332        ccc
 4333        bb
 4334        aaaaaˇ»
 4335    "});
 4336
 4337    cx.set_state(indoc! {"
 4338        Aaa«aa
 4339        cˇ»c«c
 4340        bb
 4341        aaaˇ»aa
 4342    "});
 4343    cx.update_editor(|e, window, cx| {
 4344        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4345    });
 4346    cx.assert_editor_state(indoc! {"
 4347        «Aaaaa
 4348        ccc
 4349        bbˇ»
 4350    "});
 4351
 4352    // Consider non continuous selection as distinct dedup operations
 4353    cx.set_state(indoc! {"
 4354        «aaaaa
 4355        bb
 4356        aaaaa
 4357        aaaaaˇ»
 4358
 4359        aaa«aaˇ»
 4360    "});
 4361    cx.update_editor(|e, window, cx| {
 4362        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4363    });
 4364    cx.assert_editor_state(indoc! {"
 4365        «aaaaa
 4366        bbˇ»
 4367
 4368        «aaaaaˇ»
 4369    "});
 4370}
 4371
 4372#[gpui::test]
 4373async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4374    init_test(cx, |_| {});
 4375
 4376    let mut cx = EditorTestContext::new(cx).await;
 4377
 4378    cx.set_state(indoc! {"
 4379        «Aaa
 4380        aAa
 4381        Aaaˇ»
 4382    "});
 4383    cx.update_editor(|e, window, cx| {
 4384        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4385    });
 4386    cx.assert_editor_state(indoc! {"
 4387        «Aaa
 4388        aAaˇ»
 4389    "});
 4390
 4391    cx.set_state(indoc! {"
 4392        «Aaa
 4393        aAa
 4394        aaAˇ»
 4395    "});
 4396    cx.update_editor(|e, window, cx| {
 4397        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4398    });
 4399    cx.assert_editor_state(indoc! {"
 4400        «Aaaˇ»
 4401    "});
 4402}
 4403
 4404#[gpui::test]
 4405async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4406    init_test(cx, |_| {});
 4407
 4408    let mut cx = EditorTestContext::new(cx).await;
 4409
 4410    // Manipulate with multiple selections on a single line
 4411    cx.set_state(indoc! {"
 4412        dd«dd
 4413        cˇ»c«c
 4414        bb
 4415        aaaˇ»aa
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4419    });
 4420    cx.assert_editor_state(indoc! {"
 4421        «aaaaa
 4422        bb
 4423        ccc
 4424        ddddˇ»
 4425    "});
 4426
 4427    // Manipulate with multiple disjoin selections
 4428    cx.set_state(indoc! {"
 4429 4430        4
 4431        3
 4432        2
 4433        1ˇ»
 4434
 4435        dd«dd
 4436        ccc
 4437        bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4442    });
 4443    cx.assert_editor_state(indoc! {"
 4444        «1
 4445        2
 4446        3
 4447        4
 4448        5ˇ»
 4449
 4450        «aaaaa
 4451        bb
 4452        ccc
 4453        ddddˇ»
 4454    "});
 4455
 4456    // Adding lines on each selection
 4457    cx.set_state(indoc! {"
 4458 4459        1ˇ»
 4460
 4461        bb«bb
 4462        aaaˇ»aa
 4463    "});
 4464    cx.update_editor(|e, window, cx| {
 4465        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4466    });
 4467    cx.assert_editor_state(indoc! {"
 4468        «2
 4469        1
 4470        added lineˇ»
 4471
 4472        «bbbb
 4473        aaaaa
 4474        added lineˇ»
 4475    "});
 4476
 4477    // Removing lines on each selection
 4478    cx.set_state(indoc! {"
 4479 4480        1ˇ»
 4481
 4482        bb«bb
 4483        aaaˇ»aa
 4484    "});
 4485    cx.update_editor(|e, window, cx| {
 4486        e.manipulate_immutable_lines(window, cx, |lines| {
 4487            lines.pop();
 4488        })
 4489    });
 4490    cx.assert_editor_state(indoc! {"
 4491        «2ˇ»
 4492
 4493        «bbbbˇ»
 4494    "});
 4495}
 4496
 4497#[gpui::test]
 4498async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4499    init_test(cx, |settings| {
 4500        settings.defaults.tab_size = NonZeroU32::new(3)
 4501    });
 4502
 4503    let mut cx = EditorTestContext::new(cx).await;
 4504
 4505    // MULTI SELECTION
 4506    // Ln.1 "«" tests empty lines
 4507    // Ln.9 tests just leading whitespace
 4508    cx.set_state(indoc! {"
 4509        «
 4510        abc                 // No indentationˇ»
 4511        «\tabc              // 1 tabˇ»
 4512        \t\tabc «      ˇ»   // 2 tabs
 4513        \t ab«c             // Tab followed by space
 4514         \tabc              // Space followed by tab (3 spaces should be the result)
 4515        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4516           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4517        \t
 4518        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4519    "});
 4520    cx.update_editor(|e, window, cx| {
 4521        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4522    });
 4523    cx.assert_editor_state(
 4524        indoc! {"
 4525            «
 4526            abc                 // No indentation
 4527               abc              // 1 tab
 4528                  abc          // 2 tabs
 4529                abc             // Tab followed by space
 4530               abc              // Space followed by tab (3 spaces should be the result)
 4531                           abc   // Mixed indentation (tab conversion depends on the column)
 4532               abc         // Already space indented
 4533               ·
 4534               abc\tdef          // Only the leading tab is manipulatedˇ»
 4535        "}
 4536        .replace("·", "")
 4537        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4538    );
 4539
 4540    // Test on just a few lines, the others should remain unchanged
 4541    // Only lines (3, 5, 10, 11) should change
 4542    cx.set_state(
 4543        indoc! {"
 4544            ·
 4545            abc                 // No indentation
 4546            \tabcˇ               // 1 tab
 4547            \t\tabc             // 2 tabs
 4548            \t abcˇ              // Tab followed by space
 4549             \tabc              // Space followed by tab (3 spaces should be the result)
 4550            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4551               abc              // Already space indented
 4552            «\t
 4553            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4554        "}
 4555        .replace("·", "")
 4556        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4557    );
 4558    cx.update_editor(|e, window, cx| {
 4559        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4560    });
 4561    cx.assert_editor_state(
 4562        indoc! {"
 4563            ·
 4564            abc                 // No indentation
 4565            «   abc               // 1 tabˇ»
 4566            \t\tabc             // 2 tabs
 4567            «    abc              // Tab followed by spaceˇ»
 4568             \tabc              // Space followed by tab (3 spaces should be the result)
 4569            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4570               abc              // Already space indented
 4571            «   ·
 4572               abc\tdef          // Only the leading tab is manipulatedˇ»
 4573        "}
 4574        .replace("·", "")
 4575        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4576    );
 4577
 4578    // SINGLE SELECTION
 4579    // Ln.1 "«" tests empty lines
 4580    // Ln.9 tests just leading whitespace
 4581    cx.set_state(indoc! {"
 4582        «
 4583        abc                 // No indentation
 4584        \tabc               // 1 tab
 4585        \t\tabc             // 2 tabs
 4586        \t abc              // Tab followed by space
 4587         \tabc              // Space followed by tab (3 spaces should be the result)
 4588        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4589           abc              // Already space indented
 4590        \t
 4591        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4592    "});
 4593    cx.update_editor(|e, window, cx| {
 4594        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4595    });
 4596    cx.assert_editor_state(
 4597        indoc! {"
 4598            «
 4599            abc                 // No indentation
 4600               abc               // 1 tab
 4601                  abc             // 2 tabs
 4602                abc              // Tab followed by space
 4603               abc              // Space followed by tab (3 spaces should be the result)
 4604                           abc   // Mixed indentation (tab conversion depends on the column)
 4605               abc              // Already space indented
 4606               ·
 4607               abc\tdef          // Only the leading tab is manipulatedˇ»
 4608        "}
 4609        .replace("·", "")
 4610        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4611    );
 4612}
 4613
 4614#[gpui::test]
 4615async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4616    init_test(cx, |settings| {
 4617        settings.defaults.tab_size = NonZeroU32::new(3)
 4618    });
 4619
 4620    let mut cx = EditorTestContext::new(cx).await;
 4621
 4622    // MULTI SELECTION
 4623    // Ln.1 "«" tests empty lines
 4624    // Ln.11 tests just leading whitespace
 4625    cx.set_state(indoc! {"
 4626        «
 4627        abˇ»ˇc                 // No indentation
 4628         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4629          abc  «             // 2 spaces (< 3 so dont convert)
 4630           abc              // 3 spaces (convert)
 4631             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4632        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4633        «\t abc              // Tab followed by space
 4634         \tabc              // Space followed by tab (should be consumed due to tab)
 4635        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4636           \tˇ»  «\t
 4637           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «
 4644        abc                 // No indentation
 4645         abc                // 1 space (< 3 so dont convert)
 4646          abc               // 2 spaces (< 3 so dont convert)
 4647        \tabc              // 3 spaces (convert)
 4648        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4649        \t\t\tabc           // Already tab indented
 4650        \t abc              // Tab followed by space
 4651        \tabc              // Space followed by tab (should be consumed due to tab)
 4652        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4653        \t\t\t
 4654        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4655    "});
 4656
 4657    // Test on just a few lines, the other should remain unchanged
 4658    // Only lines (4, 8, 11, 12) should change
 4659    cx.set_state(
 4660        indoc! {"
 4661            ·
 4662            abc                 // No indentation
 4663             abc                // 1 space (< 3 so dont convert)
 4664              abc               // 2 spaces (< 3 so dont convert)
 4665            «   abc              // 3 spaces (convert)ˇ»
 4666                 abc            // 5 spaces (1 tab + 2 spaces)
 4667            \t\t\tabc           // Already tab indented
 4668            \t abc              // Tab followed by space
 4669             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4670               \t\t  \tabc      // Mixed indentation
 4671            \t \t  \t   \tabc   // Mixed indentation
 4672               \t  \tˇ
 4673            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4674        "}
 4675        .replace("·", "")
 4676        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4677    );
 4678    cx.update_editor(|e, window, cx| {
 4679        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4680    });
 4681    cx.assert_editor_state(
 4682        indoc! {"
 4683            ·
 4684            abc                 // No indentation
 4685             abc                // 1 space (< 3 so dont convert)
 4686              abc               // 2 spaces (< 3 so dont convert)
 4687            «\tabc              // 3 spaces (convert)ˇ»
 4688                 abc            // 5 spaces (1 tab + 2 spaces)
 4689            \t\t\tabc           // Already tab indented
 4690            \t abc              // Tab followed by space
 4691            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4692               \t\t  \tabc      // Mixed indentation
 4693            \t \t  \t   \tabc   // Mixed indentation
 4694            «\t\t\t
 4695            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4696        "}
 4697        .replace("·", "")
 4698        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4699    );
 4700
 4701    // SINGLE SELECTION
 4702    // Ln.1 "«" tests empty lines
 4703    // Ln.11 tests just leading whitespace
 4704    cx.set_state(indoc! {"
 4705        «
 4706        abc                 // No indentation
 4707         abc                // 1 space (< 3 so dont convert)
 4708          abc               // 2 spaces (< 3 so dont convert)
 4709           abc              // 3 spaces (convert)
 4710             abc            // 5 spaces (1 tab + 2 spaces)
 4711        \t\t\tabc           // Already tab indented
 4712        \t abc              // Tab followed by space
 4713         \tabc              // Space followed by tab (should be consumed due to tab)
 4714        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4715           \t  \t
 4716           abc   \t         // Only the leading spaces should be convertedˇ»
 4717    "});
 4718    cx.update_editor(|e, window, cx| {
 4719        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4720    });
 4721    cx.assert_editor_state(indoc! {"
 4722        «
 4723        abc                 // No indentation
 4724         abc                // 1 space (< 3 so dont convert)
 4725          abc               // 2 spaces (< 3 so dont convert)
 4726        \tabc              // 3 spaces (convert)
 4727        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4728        \t\t\tabc           // Already tab indented
 4729        \t abc              // Tab followed by space
 4730        \tabc              // Space followed by tab (should be consumed due to tab)
 4731        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4732        \t\t\t
 4733        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4734    "});
 4735}
 4736
 4737#[gpui::test]
 4738async fn test_toggle_case(cx: &mut TestAppContext) {
 4739    init_test(cx, |_| {});
 4740
 4741    let mut cx = EditorTestContext::new(cx).await;
 4742
 4743    // If all lower case -> upper case
 4744    cx.set_state(indoc! {"
 4745        «hello worldˇ»
 4746    "});
 4747    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4748    cx.assert_editor_state(indoc! {"
 4749        «HELLO WORLDˇ»
 4750    "});
 4751
 4752    // If all upper case -> lower case
 4753    cx.set_state(indoc! {"
 4754        «HELLO WORLDˇ»
 4755    "});
 4756    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4757    cx.assert_editor_state(indoc! {"
 4758        «hello worldˇ»
 4759    "});
 4760
 4761    // If any upper case characters are identified -> lower case
 4762    // This matches JetBrains IDEs
 4763    cx.set_state(indoc! {"
 4764        «hEllo worldˇ»
 4765    "});
 4766    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4767    cx.assert_editor_state(indoc! {"
 4768        «hello worldˇ»
 4769    "});
 4770}
 4771
 4772#[gpui::test]
 4773async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4774    init_test(cx, |_| {});
 4775
 4776    let mut cx = EditorTestContext::new(cx).await;
 4777
 4778    cx.set_state(indoc! {"
 4779        «implement-windows-supportˇ»
 4780    "});
 4781    cx.update_editor(|e, window, cx| {
 4782        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4783    });
 4784    cx.assert_editor_state(indoc! {"
 4785        «Implement windows supportˇ»
 4786    "});
 4787}
 4788
 4789#[gpui::test]
 4790async fn test_manipulate_text(cx: &mut TestAppContext) {
 4791    init_test(cx, |_| {});
 4792
 4793    let mut cx = EditorTestContext::new(cx).await;
 4794
 4795    // Test convert_to_upper_case()
 4796    cx.set_state(indoc! {"
 4797        «hello worldˇ»
 4798    "});
 4799    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4800    cx.assert_editor_state(indoc! {"
 4801        «HELLO WORLDˇ»
 4802    "});
 4803
 4804    // Test convert_to_lower_case()
 4805    cx.set_state(indoc! {"
 4806        «HELLO WORLDˇ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «hello worldˇ»
 4811    "});
 4812
 4813    // Test multiple line, single selection case
 4814    cx.set_state(indoc! {"
 4815        «The quick brown
 4816        fox jumps over
 4817        the lazy dogˇ»
 4818    "});
 4819    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4820    cx.assert_editor_state(indoc! {"
 4821        «The Quick Brown
 4822        Fox Jumps Over
 4823        The Lazy Dogˇ»
 4824    "});
 4825
 4826    // Test multiple line, single selection case
 4827    cx.set_state(indoc! {"
 4828        «The quick brown
 4829        fox jumps over
 4830        the lazy dogˇ»
 4831    "});
 4832    cx.update_editor(|e, window, cx| {
 4833        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4834    });
 4835    cx.assert_editor_state(indoc! {"
 4836        «TheQuickBrown
 4837        FoxJumpsOver
 4838        TheLazyDogˇ»
 4839    "});
 4840
 4841    // From here on out, test more complex cases of manipulate_text()
 4842
 4843    // Test no selection case - should affect words cursors are in
 4844    // Cursor at beginning, middle, and end of word
 4845    cx.set_state(indoc! {"
 4846        ˇhello big beauˇtiful worldˇ
 4847    "});
 4848    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4849    cx.assert_editor_state(indoc! {"
 4850        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4851    "});
 4852
 4853    // Test multiple selections on a single line and across multiple lines
 4854    cx.set_state(indoc! {"
 4855        «Theˇ» quick «brown
 4856        foxˇ» jumps «overˇ»
 4857        the «lazyˇ» dog
 4858    "});
 4859    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4860    cx.assert_editor_state(indoc! {"
 4861        «THEˇ» quick «BROWN
 4862        FOXˇ» jumps «OVERˇ»
 4863        the «LAZYˇ» dog
 4864    "});
 4865
 4866    // Test case where text length grows
 4867    cx.set_state(indoc! {"
 4868        «tschüߡ»
 4869    "});
 4870    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4871    cx.assert_editor_state(indoc! {"
 4872        «TSCHÜSSˇ»
 4873    "});
 4874
 4875    // Test to make sure we don't crash when text shrinks
 4876    cx.set_state(indoc! {"
 4877        aaa_bbbˇ
 4878    "});
 4879    cx.update_editor(|e, window, cx| {
 4880        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4881    });
 4882    cx.assert_editor_state(indoc! {"
 4883        «aaaBbbˇ»
 4884    "});
 4885
 4886    // Test to make sure we all aware of the fact that each word can grow and shrink
 4887    // Final selections should be aware of this fact
 4888    cx.set_state(indoc! {"
 4889        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4890    "});
 4891    cx.update_editor(|e, window, cx| {
 4892        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4893    });
 4894    cx.assert_editor_state(indoc! {"
 4895        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4896    "});
 4897
 4898    cx.set_state(indoc! {"
 4899        «hElLo, WoRld!ˇ»
 4900    "});
 4901    cx.update_editor(|e, window, cx| {
 4902        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4903    });
 4904    cx.assert_editor_state(indoc! {"
 4905        «HeLlO, wOrLD!ˇ»
 4906    "});
 4907}
 4908
 4909#[gpui::test]
 4910fn test_duplicate_line(cx: &mut TestAppContext) {
 4911    init_test(cx, |_| {});
 4912
 4913    let editor = cx.add_window(|window, cx| {
 4914        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4915        build_editor(buffer, window, cx)
 4916    });
 4917    _ = editor.update(cx, |editor, window, cx| {
 4918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4919            s.select_display_ranges([
 4920                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4921                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4922                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4923                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4924            ])
 4925        });
 4926        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4927        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4928        assert_eq!(
 4929            editor.selections.display_ranges(cx),
 4930            vec![
 4931                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4932                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4933                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4934                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4935            ]
 4936        );
 4937    });
 4938
 4939    let editor = cx.add_window(|window, cx| {
 4940        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4941        build_editor(buffer, window, cx)
 4942    });
 4943    _ = editor.update(cx, |editor, window, cx| {
 4944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4945            s.select_display_ranges([
 4946                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4947                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4948            ])
 4949        });
 4950        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4951        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4952        assert_eq!(
 4953            editor.selections.display_ranges(cx),
 4954            vec![
 4955                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4956                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4957            ]
 4958        );
 4959    });
 4960
 4961    // With `move_upwards` the selections stay in place, except for
 4962    // the lines inserted above them
 4963    let editor = cx.add_window(|window, cx| {
 4964        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4965        build_editor(buffer, window, cx)
 4966    });
 4967    _ = editor.update(cx, |editor, window, cx| {
 4968        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4969            s.select_display_ranges([
 4970                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4971                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4972                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4973                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4974            ])
 4975        });
 4976        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4977        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4978        assert_eq!(
 4979            editor.selections.display_ranges(cx),
 4980            vec![
 4981                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4982                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4983                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4984                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4985            ]
 4986        );
 4987    });
 4988
 4989    let editor = cx.add_window(|window, cx| {
 4990        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4991        build_editor(buffer, window, cx)
 4992    });
 4993    _ = editor.update(cx, |editor, window, cx| {
 4994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4995            s.select_display_ranges([
 4996                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4997                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4998            ])
 4999        });
 5000        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5001        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5002        assert_eq!(
 5003            editor.selections.display_ranges(cx),
 5004            vec![
 5005                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5006                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5007            ]
 5008        );
 5009    });
 5010
 5011    let editor = cx.add_window(|window, cx| {
 5012        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5013        build_editor(buffer, window, cx)
 5014    });
 5015    _ = editor.update(cx, |editor, window, cx| {
 5016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5017            s.select_display_ranges([
 5018                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5019                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5020            ])
 5021        });
 5022        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5023        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5024        assert_eq!(
 5025            editor.selections.display_ranges(cx),
 5026            vec![
 5027                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5028                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5029            ]
 5030        );
 5031    });
 5032}
 5033
 5034#[gpui::test]
 5035fn test_move_line_up_down(cx: &mut TestAppContext) {
 5036    init_test(cx, |_| {});
 5037
 5038    let editor = cx.add_window(|window, cx| {
 5039        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5040        build_editor(buffer, window, cx)
 5041    });
 5042    _ = editor.update(cx, |editor, window, cx| {
 5043        editor.fold_creases(
 5044            vec![
 5045                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5046                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5047                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5048            ],
 5049            true,
 5050            window,
 5051            cx,
 5052        );
 5053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5054            s.select_display_ranges([
 5055                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5056                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5057                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5058                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5059            ])
 5060        });
 5061        assert_eq!(
 5062            editor.display_text(cx),
 5063            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5064        );
 5065
 5066        editor.move_line_up(&MoveLineUp, window, cx);
 5067        assert_eq!(
 5068            editor.display_text(cx),
 5069            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5070        );
 5071        assert_eq!(
 5072            editor.selections.display_ranges(cx),
 5073            vec![
 5074                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5075                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5076                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5077                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5078            ]
 5079        );
 5080    });
 5081
 5082    _ = editor.update(cx, |editor, window, cx| {
 5083        editor.move_line_down(&MoveLineDown, window, cx);
 5084        assert_eq!(
 5085            editor.display_text(cx),
 5086            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5087        );
 5088        assert_eq!(
 5089            editor.selections.display_ranges(cx),
 5090            vec![
 5091                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5092                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5093                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5094                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5095            ]
 5096        );
 5097    });
 5098
 5099    _ = editor.update(cx, |editor, window, cx| {
 5100        editor.move_line_down(&MoveLineDown, window, cx);
 5101        assert_eq!(
 5102            editor.display_text(cx),
 5103            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5104        );
 5105        assert_eq!(
 5106            editor.selections.display_ranges(cx),
 5107            vec![
 5108                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5109                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5110                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5111                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5112            ]
 5113        );
 5114    });
 5115
 5116    _ = editor.update(cx, |editor, window, cx| {
 5117        editor.move_line_up(&MoveLineUp, window, cx);
 5118        assert_eq!(
 5119            editor.display_text(cx),
 5120            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5121        );
 5122        assert_eq!(
 5123            editor.selections.display_ranges(cx),
 5124            vec![
 5125                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5126                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5127                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5128                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5129            ]
 5130        );
 5131    });
 5132}
 5133
 5134#[gpui::test]
 5135fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5136    init_test(cx, |_| {});
 5137    let editor = cx.add_window(|window, cx| {
 5138        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5139        build_editor(buffer, window, cx)
 5140    });
 5141    _ = editor.update(cx, |editor, window, cx| {
 5142        editor.fold_creases(
 5143            vec![Crease::simple(
 5144                Point::new(6, 4)..Point::new(7, 4),
 5145                FoldPlaceholder::test(),
 5146            )],
 5147            true,
 5148            window,
 5149            cx,
 5150        );
 5151        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5152            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5153        });
 5154        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5155        editor.move_line_up(&MoveLineUp, window, cx);
 5156        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5157        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5158    });
 5159}
 5160
 5161#[gpui::test]
 5162fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5163    init_test(cx, |_| {});
 5164
 5165    let editor = cx.add_window(|window, cx| {
 5166        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5167        build_editor(buffer, window, cx)
 5168    });
 5169    _ = editor.update(cx, |editor, window, cx| {
 5170        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5171        editor.insert_blocks(
 5172            [BlockProperties {
 5173                style: BlockStyle::Fixed,
 5174                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5175                height: Some(1),
 5176                render: Arc::new(|_| div().into_any()),
 5177                priority: 0,
 5178            }],
 5179            Some(Autoscroll::fit()),
 5180            cx,
 5181        );
 5182        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5183            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5184        });
 5185        editor.move_line_down(&MoveLineDown, window, cx);
 5186    });
 5187}
 5188
 5189#[gpui::test]
 5190async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5191    init_test(cx, |_| {});
 5192
 5193    let mut cx = EditorTestContext::new(cx).await;
 5194    cx.set_state(
 5195        &"
 5196            ˇzero
 5197            one
 5198            two
 5199            three
 5200            four
 5201            five
 5202        "
 5203        .unindent(),
 5204    );
 5205
 5206    // Create a four-line block that replaces three lines of text.
 5207    cx.update_editor(|editor, window, cx| {
 5208        let snapshot = editor.snapshot(window, cx);
 5209        let snapshot = &snapshot.buffer_snapshot;
 5210        let placement = BlockPlacement::Replace(
 5211            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5212        );
 5213        editor.insert_blocks(
 5214            [BlockProperties {
 5215                placement,
 5216                height: Some(4),
 5217                style: BlockStyle::Sticky,
 5218                render: Arc::new(|_| gpui::div().into_any_element()),
 5219                priority: 0,
 5220            }],
 5221            None,
 5222            cx,
 5223        );
 5224    });
 5225
 5226    // Move down so that the cursor touches the block.
 5227    cx.update_editor(|editor, window, cx| {
 5228        editor.move_down(&Default::default(), window, cx);
 5229    });
 5230    cx.assert_editor_state(
 5231        &"
 5232            zero
 5233            «one
 5234            two
 5235            threeˇ»
 5236            four
 5237            five
 5238        "
 5239        .unindent(),
 5240    );
 5241
 5242    // Move down past the block.
 5243    cx.update_editor(|editor, window, cx| {
 5244        editor.move_down(&Default::default(), window, cx);
 5245    });
 5246    cx.assert_editor_state(
 5247        &"
 5248            zero
 5249            one
 5250            two
 5251            three
 5252            ˇfour
 5253            five
 5254        "
 5255        .unindent(),
 5256    );
 5257}
 5258
 5259#[gpui::test]
 5260fn test_transpose(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262
 5263    _ = cx.add_window(|window, cx| {
 5264        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5265        editor.set_style(EditorStyle::default(), window, cx);
 5266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5267            s.select_ranges([1..1])
 5268        });
 5269        editor.transpose(&Default::default(), window, cx);
 5270        assert_eq!(editor.text(cx), "bac");
 5271        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5272
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bca");
 5275        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bac");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5280
 5281        editor
 5282    });
 5283
 5284    _ = cx.add_window(|window, cx| {
 5285        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5286        editor.set_style(EditorStyle::default(), window, cx);
 5287        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5288            s.select_ranges([3..3])
 5289        });
 5290        editor.transpose(&Default::default(), window, cx);
 5291        assert_eq!(editor.text(cx), "acb\nde");
 5292        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5293
 5294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5295            s.select_ranges([4..4])
 5296        });
 5297        editor.transpose(&Default::default(), window, cx);
 5298        assert_eq!(editor.text(cx), "acbd\ne");
 5299        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5300
 5301        editor.transpose(&Default::default(), window, cx);
 5302        assert_eq!(editor.text(cx), "acbde\n");
 5303        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5304
 5305        editor.transpose(&Default::default(), window, cx);
 5306        assert_eq!(editor.text(cx), "acbd\ne");
 5307        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5308
 5309        editor
 5310    });
 5311
 5312    _ = cx.add_window(|window, cx| {
 5313        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5314        editor.set_style(EditorStyle::default(), window, cx);
 5315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5316            s.select_ranges([1..1, 2..2, 4..4])
 5317        });
 5318        editor.transpose(&Default::default(), window, cx);
 5319        assert_eq!(editor.text(cx), "bacd\ne");
 5320        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5321
 5322        editor.transpose(&Default::default(), window, cx);
 5323        assert_eq!(editor.text(cx), "bcade\n");
 5324        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5325
 5326        editor.transpose(&Default::default(), window, cx);
 5327        assert_eq!(editor.text(cx), "bcda\ne");
 5328        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5329
 5330        editor.transpose(&Default::default(), window, cx);
 5331        assert_eq!(editor.text(cx), "bcade\n");
 5332        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5333
 5334        editor.transpose(&Default::default(), window, cx);
 5335        assert_eq!(editor.text(cx), "bcaed\n");
 5336        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5337
 5338        editor
 5339    });
 5340
 5341    _ = cx.add_window(|window, cx| {
 5342        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5343        editor.set_style(EditorStyle::default(), window, cx);
 5344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5345            s.select_ranges([4..4])
 5346        });
 5347        editor.transpose(&Default::default(), window, cx);
 5348        assert_eq!(editor.text(cx), "🏀🍐✋");
 5349        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5350
 5351        editor.transpose(&Default::default(), window, cx);
 5352        assert_eq!(editor.text(cx), "🏀✋🍐");
 5353        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5354
 5355        editor.transpose(&Default::default(), window, cx);
 5356        assert_eq!(editor.text(cx), "🏀🍐✋");
 5357        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5358
 5359        editor
 5360    });
 5361}
 5362
 5363#[gpui::test]
 5364async fn test_rewrap(cx: &mut TestAppContext) {
 5365    init_test(cx, |settings| {
 5366        settings.languages.0.extend([
 5367            (
 5368                "Markdown".into(),
 5369                LanguageSettingsContent {
 5370                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5371                    preferred_line_length: Some(40),
 5372                    ..Default::default()
 5373                },
 5374            ),
 5375            (
 5376                "Plain Text".into(),
 5377                LanguageSettingsContent {
 5378                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5379                    preferred_line_length: Some(40),
 5380                    ..Default::default()
 5381                },
 5382            ),
 5383            (
 5384                "C++".into(),
 5385                LanguageSettingsContent {
 5386                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5387                    preferred_line_length: Some(40),
 5388                    ..Default::default()
 5389                },
 5390            ),
 5391            (
 5392                "Python".into(),
 5393                LanguageSettingsContent {
 5394                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5395                    preferred_line_length: Some(40),
 5396                    ..Default::default()
 5397                },
 5398            ),
 5399            (
 5400                "Rust".into(),
 5401                LanguageSettingsContent {
 5402                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5403                    preferred_line_length: Some(40),
 5404                    ..Default::default()
 5405                },
 5406            ),
 5407        ])
 5408    });
 5409
 5410    let mut cx = EditorTestContext::new(cx).await;
 5411
 5412    let cpp_language = Arc::new(Language::new(
 5413        LanguageConfig {
 5414            name: "C++".into(),
 5415            line_comments: vec!["// ".into()],
 5416            ..LanguageConfig::default()
 5417        },
 5418        None,
 5419    ));
 5420    let python_language = Arc::new(Language::new(
 5421        LanguageConfig {
 5422            name: "Python".into(),
 5423            line_comments: vec!["# ".into()],
 5424            ..LanguageConfig::default()
 5425        },
 5426        None,
 5427    ));
 5428    let markdown_language = Arc::new(Language::new(
 5429        LanguageConfig {
 5430            name: "Markdown".into(),
 5431            rewrap_prefixes: vec![
 5432                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5433                regex::Regex::new("[-*+]\\s+").unwrap(),
 5434            ],
 5435            ..LanguageConfig::default()
 5436        },
 5437        None,
 5438    ));
 5439    let rust_language = Arc::new(Language::new(
 5440        LanguageConfig {
 5441            name: "Rust".into(),
 5442            line_comments: vec!["// ".into(), "/// ".into()],
 5443            ..LanguageConfig::default()
 5444        },
 5445        Some(tree_sitter_rust::LANGUAGE.into()),
 5446    ));
 5447
 5448    let plaintext_language = Arc::new(Language::new(
 5449        LanguageConfig {
 5450            name: "Plain Text".into(),
 5451            ..LanguageConfig::default()
 5452        },
 5453        None,
 5454    ));
 5455
 5456    // Test basic rewrapping of a long line with a cursor
 5457    assert_rewrap(
 5458        indoc! {"
 5459            // ˇThis is a long comment that needs to be wrapped.
 5460        "},
 5461        indoc! {"
 5462            // ˇThis is a long comment that needs to
 5463            // be wrapped.
 5464        "},
 5465        cpp_language.clone(),
 5466        &mut cx,
 5467    );
 5468
 5469    // Test rewrapping a full selection
 5470    assert_rewrap(
 5471        indoc! {"
 5472            «// This selected long comment needs to be wrapped.ˇ»"
 5473        },
 5474        indoc! {"
 5475            «// This selected long comment needs to
 5476            // be wrapped.ˇ»"
 5477        },
 5478        cpp_language.clone(),
 5479        &mut cx,
 5480    );
 5481
 5482    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5483    assert_rewrap(
 5484        indoc! {"
 5485            // ˇThis is the first line.
 5486            // Thisˇ is the second line.
 5487            // This is the thirdˇ line, all part of one paragraph.
 5488         "},
 5489        indoc! {"
 5490            // ˇThis is the first line. Thisˇ is the
 5491            // second line. This is the thirdˇ line,
 5492            // all part of one paragraph.
 5493         "},
 5494        cpp_language.clone(),
 5495        &mut cx,
 5496    );
 5497
 5498    // Test multiple cursors in different paragraphs trigger separate rewraps
 5499    assert_rewrap(
 5500        indoc! {"
 5501            // ˇThis is the first paragraph, first line.
 5502            // ˇThis is the first paragraph, second line.
 5503
 5504            // ˇThis is the second paragraph, first line.
 5505            // ˇThis is the second paragraph, second line.
 5506        "},
 5507        indoc! {"
 5508            // ˇThis is the first paragraph, first
 5509            // line. ˇThis is the first paragraph,
 5510            // second line.
 5511
 5512            // ˇThis is the second paragraph, first
 5513            // line. ˇThis is the second paragraph,
 5514            // second line.
 5515        "},
 5516        cpp_language.clone(),
 5517        &mut cx,
 5518    );
 5519
 5520    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5521    assert_rewrap(
 5522        indoc! {"
 5523            «// A regular long long comment to be wrapped.
 5524            /// A documentation long comment to be wrapped.ˇ»
 5525          "},
 5526        indoc! {"
 5527            «// A regular long long comment to be
 5528            // wrapped.
 5529            /// A documentation long comment to be
 5530            /// wrapped.ˇ»
 5531          "},
 5532        rust_language.clone(),
 5533        &mut cx,
 5534    );
 5535
 5536    // Test that change in indentation level trigger seperate rewraps
 5537    assert_rewrap(
 5538        indoc! {"
 5539            fn foo() {
 5540                «// This is a long comment at the base indent.
 5541                    // This is a long comment at the next indent.ˇ»
 5542            }
 5543        "},
 5544        indoc! {"
 5545            fn foo() {
 5546                «// This is a long comment at the
 5547                // base indent.
 5548                    // This is a long comment at the
 5549                    // next indent.ˇ»
 5550            }
 5551        "},
 5552        rust_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5557    assert_rewrap(
 5558        indoc! {"
 5559            # ˇThis is a long comment using a pound sign.
 5560        "},
 5561        indoc! {"
 5562            # ˇThis is a long comment using a pound
 5563            # sign.
 5564        "},
 5565        python_language.clone(),
 5566        &mut cx,
 5567    );
 5568
 5569    // Test rewrapping only affects comments, not code even when selected
 5570    assert_rewrap(
 5571        indoc! {"
 5572            «/// This doc comment is long and should be wrapped.
 5573            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5574        "},
 5575        indoc! {"
 5576            «/// This doc comment is long and should
 5577            /// be wrapped.
 5578            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5579        "},
 5580        rust_language.clone(),
 5581        &mut cx,
 5582    );
 5583
 5584    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5585    assert_rewrap(
 5586        indoc! {"
 5587            # Header
 5588
 5589            A long long long line of markdown text to wrap.ˇ
 5590         "},
 5591        indoc! {"
 5592            # Header
 5593
 5594            A long long long line of markdown text
 5595            to wrap.ˇ
 5596         "},
 5597        markdown_language.clone(),
 5598        &mut cx,
 5599    );
 5600
 5601    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5602    assert_rewrap(
 5603        indoc! {"
 5604            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5605            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5606            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5607        "},
 5608        indoc! {"
 5609            «1. This is a numbered list item that is
 5610               very long and needs to be wrapped
 5611               properly.
 5612            2. This is a numbered list item that is
 5613               very long and needs to be wrapped
 5614               properly.
 5615            - This is an unordered list item that is
 5616              also very long and should not merge
 5617              with the numbered item.ˇ»
 5618        "},
 5619        markdown_language.clone(),
 5620        &mut cx,
 5621    );
 5622
 5623    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5624    assert_rewrap(
 5625        indoc! {"
 5626            «1. This is a numbered list item that is
 5627            very long and needs to be wrapped
 5628            properly.
 5629            2. This is a numbered list item that is
 5630            very long and needs to be wrapped
 5631            properly.
 5632            - This is an unordered list item that is
 5633            also very long and should not merge with
 5634            the numbered item.ˇ»
 5635        "},
 5636        indoc! {"
 5637            «1. This is a numbered list item that is
 5638               very long and needs to be wrapped
 5639               properly.
 5640            2. This is a numbered list item that is
 5641               very long and needs to be wrapped
 5642               properly.
 5643            - This is an unordered list item that is
 5644              also very long and should not merge
 5645              with the numbered item.ˇ»
 5646        "},
 5647        markdown_language.clone(),
 5648        &mut cx,
 5649    );
 5650
 5651    // Test that rewrapping maintain indents even when they already exists.
 5652    assert_rewrap(
 5653        indoc! {"
 5654            «1. This is a numbered list
 5655               item that is very long and needs to be wrapped properly.
 5656            2. This is a numbered list
 5657               item that is very long and needs to be wrapped properly.
 5658            - This is an unordered list item that is also very long and
 5659              should not merge with the numbered item.ˇ»
 5660        "},
 5661        indoc! {"
 5662            «1. This is a numbered list item that is
 5663               very long and needs to be wrapped
 5664               properly.
 5665            2. This is a numbered list item that is
 5666               very long and needs to be wrapped
 5667               properly.
 5668            - This is an unordered list item that is
 5669              also very long and should not merge
 5670              with the numbered item.ˇ»
 5671        "},
 5672        markdown_language.clone(),
 5673        &mut cx,
 5674    );
 5675
 5676    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5677    assert_rewrap(
 5678        indoc! {"
 5679            ˇThis is a very long line of plain text that will be wrapped.
 5680        "},
 5681        indoc! {"
 5682            ˇThis is a very long line of plain text
 5683            that will be wrapped.
 5684        "},
 5685        plaintext_language.clone(),
 5686        &mut cx,
 5687    );
 5688
 5689    // Test that non-commented code acts as a paragraph boundary within a selection
 5690    assert_rewrap(
 5691        indoc! {"
 5692               «// This is the first long comment block to be wrapped.
 5693               fn my_func(a: u32);
 5694               // This is the second long comment block to be wrapped.ˇ»
 5695           "},
 5696        indoc! {"
 5697               «// This is the first long comment block
 5698               // to be wrapped.
 5699               fn my_func(a: u32);
 5700               // This is the second long comment block
 5701               // to be wrapped.ˇ»
 5702           "},
 5703        rust_language.clone(),
 5704        &mut cx,
 5705    );
 5706
 5707    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5708    assert_rewrap(
 5709        indoc! {"
 5710            «ˇThis is a very long line that will be wrapped.
 5711
 5712            This is another paragraph in the same selection.»
 5713
 5714            «\tThis is a very long indented line that will be wrapped.ˇ»
 5715         "},
 5716        indoc! {"
 5717            «ˇThis is a very long line that will be
 5718            wrapped.
 5719
 5720            This is another paragraph in the same
 5721            selection.»
 5722
 5723            «\tThis is a very long indented line
 5724            \tthat will be wrapped.ˇ»
 5725         "},
 5726        plaintext_language.clone(),
 5727        &mut cx,
 5728    );
 5729
 5730    // Test that an empty comment line acts as a paragraph boundary
 5731    assert_rewrap(
 5732        indoc! {"
 5733            // ˇThis is a long comment that will be wrapped.
 5734            //
 5735            // And this is another long comment that will also be wrapped.ˇ
 5736         "},
 5737        indoc! {"
 5738            // ˇThis is a long comment that will be
 5739            // wrapped.
 5740            //
 5741            // And this is another long comment that
 5742            // will also be wrapped.ˇ
 5743         "},
 5744        cpp_language,
 5745        &mut cx,
 5746    );
 5747
 5748    #[track_caller]
 5749    fn assert_rewrap(
 5750        unwrapped_text: &str,
 5751        wrapped_text: &str,
 5752        language: Arc<Language>,
 5753        cx: &mut EditorTestContext,
 5754    ) {
 5755        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5756        cx.set_state(unwrapped_text);
 5757        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5758        cx.assert_editor_state(wrapped_text);
 5759    }
 5760}
 5761
 5762#[gpui::test]
 5763async fn test_hard_wrap(cx: &mut TestAppContext) {
 5764    init_test(cx, |_| {});
 5765    let mut cx = EditorTestContext::new(cx).await;
 5766
 5767    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5768    cx.update_editor(|editor, _, cx| {
 5769        editor.set_hard_wrap(Some(14), cx);
 5770    });
 5771
 5772    cx.set_state(indoc!(
 5773        "
 5774        one two three ˇ
 5775        "
 5776    ));
 5777    cx.simulate_input("four");
 5778    cx.run_until_parked();
 5779
 5780    cx.assert_editor_state(indoc!(
 5781        "
 5782        one two three
 5783        fourˇ
 5784        "
 5785    ));
 5786
 5787    cx.update_editor(|editor, window, cx| {
 5788        editor.newline(&Default::default(), window, cx);
 5789    });
 5790    cx.run_until_parked();
 5791    cx.assert_editor_state(indoc!(
 5792        "
 5793        one two three
 5794        four
 5795        ˇ
 5796        "
 5797    ));
 5798
 5799    cx.simulate_input("five");
 5800    cx.run_until_parked();
 5801    cx.assert_editor_state(indoc!(
 5802        "
 5803        one two three
 5804        four
 5805        fiveˇ
 5806        "
 5807    ));
 5808
 5809    cx.update_editor(|editor, window, cx| {
 5810        editor.newline(&Default::default(), window, cx);
 5811    });
 5812    cx.run_until_parked();
 5813    cx.simulate_input("# ");
 5814    cx.run_until_parked();
 5815    cx.assert_editor_state(indoc!(
 5816        "
 5817        one two three
 5818        four
 5819        five
 5820        # ˇ
 5821        "
 5822    ));
 5823
 5824    cx.update_editor(|editor, window, cx| {
 5825        editor.newline(&Default::default(), window, cx);
 5826    });
 5827    cx.run_until_parked();
 5828    cx.assert_editor_state(indoc!(
 5829        "
 5830        one two three
 5831        four
 5832        five
 5833        #\x20
 5834 5835        "
 5836    ));
 5837
 5838    cx.simulate_input(" 6");
 5839    cx.run_until_parked();
 5840    cx.assert_editor_state(indoc!(
 5841        "
 5842        one two three
 5843        four
 5844        five
 5845        #
 5846        # 6ˇ
 5847        "
 5848    ));
 5849}
 5850
 5851#[gpui::test]
 5852async fn test_clipboard(cx: &mut TestAppContext) {
 5853    init_test(cx, |_| {});
 5854
 5855    let mut cx = EditorTestContext::new(cx).await;
 5856
 5857    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5858    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5859    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5860
 5861    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5862    cx.set_state("two ˇfour ˇsix ˇ");
 5863    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5864    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5865
 5866    // Paste again but with only two cursors. Since the number of cursors doesn't
 5867    // match the number of slices in the clipboard, the entire clipboard text
 5868    // is pasted at each cursor.
 5869    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5870    cx.update_editor(|e, window, cx| {
 5871        e.handle_input("( ", window, cx);
 5872        e.paste(&Paste, window, cx);
 5873        e.handle_input(") ", window, cx);
 5874    });
 5875    cx.assert_editor_state(
 5876        &([
 5877            "( one✅ ",
 5878            "three ",
 5879            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5880            "three ",
 5881            "five ) ˇ",
 5882        ]
 5883        .join("\n")),
 5884    );
 5885
 5886    // Cut with three selections, one of which is full-line.
 5887    cx.set_state(indoc! {"
 5888        1«2ˇ»3
 5889        4ˇ567
 5890        «8ˇ»9"});
 5891    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5892    cx.assert_editor_state(indoc! {"
 5893        1ˇ3
 5894        ˇ9"});
 5895
 5896    // Paste with three selections, noticing how the copied selection that was full-line
 5897    // gets inserted before the second cursor.
 5898    cx.set_state(indoc! {"
 5899        1ˇ3
 5900 5901        «oˇ»ne"});
 5902    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5903    cx.assert_editor_state(indoc! {"
 5904        12ˇ3
 5905        4567
 5906 5907        8ˇne"});
 5908
 5909    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5910    cx.set_state(indoc! {"
 5911        The quick brown
 5912        fox juˇmps over
 5913        the lazy dog"});
 5914    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5915    assert_eq!(
 5916        cx.read_from_clipboard()
 5917            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5918        Some("fox jumps over\n".to_string())
 5919    );
 5920
 5921    // Paste with three selections, noticing how the copied full-line selection is inserted
 5922    // before the empty selections but replaces the selection that is non-empty.
 5923    cx.set_state(indoc! {"
 5924        Tˇhe quick brown
 5925        «foˇ»x jumps over
 5926        tˇhe lazy dog"});
 5927    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5928    cx.assert_editor_state(indoc! {"
 5929        fox jumps over
 5930        Tˇhe quick brown
 5931        fox jumps over
 5932        ˇx jumps over
 5933        fox jumps over
 5934        tˇhe lazy dog"});
 5935}
 5936
 5937#[gpui::test]
 5938async fn test_copy_trim(cx: &mut TestAppContext) {
 5939    init_test(cx, |_| {});
 5940
 5941    let mut cx = EditorTestContext::new(cx).await;
 5942    cx.set_state(
 5943        r#"            «for selection in selections.iter() {
 5944            let mut start = selection.start;
 5945            let mut end = selection.end;
 5946            let is_entire_line = selection.is_empty();
 5947            if is_entire_line {
 5948                start = Point::new(start.row, 0);ˇ»
 5949                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5950            }
 5951        "#,
 5952    );
 5953    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5954    assert_eq!(
 5955        cx.read_from_clipboard()
 5956            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5957        Some(
 5958            "for selection in selections.iter() {
 5959            let mut start = selection.start;
 5960            let mut end = selection.end;
 5961            let is_entire_line = selection.is_empty();
 5962            if is_entire_line {
 5963                start = Point::new(start.row, 0);"
 5964                .to_string()
 5965        ),
 5966        "Regular copying preserves all indentation selected",
 5967    );
 5968    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5969    assert_eq!(
 5970        cx.read_from_clipboard()
 5971            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5972        Some(
 5973            "for selection in selections.iter() {
 5974let mut start = selection.start;
 5975let mut end = selection.end;
 5976let is_entire_line = selection.is_empty();
 5977if is_entire_line {
 5978    start = Point::new(start.row, 0);"
 5979                .to_string()
 5980        ),
 5981        "Copying with stripping should strip all leading whitespaces"
 5982    );
 5983
 5984    cx.set_state(
 5985        r#"       «     for selection in selections.iter() {
 5986            let mut start = selection.start;
 5987            let mut end = selection.end;
 5988            let is_entire_line = selection.is_empty();
 5989            if is_entire_line {
 5990                start = Point::new(start.row, 0);ˇ»
 5991                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5992            }
 5993        "#,
 5994    );
 5995    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5996    assert_eq!(
 5997        cx.read_from_clipboard()
 5998            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5999        Some(
 6000            "     for selection in selections.iter() {
 6001            let mut start = selection.start;
 6002            let mut end = selection.end;
 6003            let is_entire_line = selection.is_empty();
 6004            if is_entire_line {
 6005                start = Point::new(start.row, 0);"
 6006                .to_string()
 6007        ),
 6008        "Regular copying preserves all indentation selected",
 6009    );
 6010    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6011    assert_eq!(
 6012        cx.read_from_clipboard()
 6013            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6014        Some(
 6015            "for selection in selections.iter() {
 6016let mut start = selection.start;
 6017let mut end = selection.end;
 6018let is_entire_line = selection.is_empty();
 6019if is_entire_line {
 6020    start = Point::new(start.row, 0);"
 6021                .to_string()
 6022        ),
 6023        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6024    );
 6025
 6026    cx.set_state(
 6027        r#"       «ˇ     for selection in selections.iter() {
 6028            let mut start = selection.start;
 6029            let mut end = selection.end;
 6030            let is_entire_line = selection.is_empty();
 6031            if is_entire_line {
 6032                start = Point::new(start.row, 0);»
 6033                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6034            }
 6035        "#,
 6036    );
 6037    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6038    assert_eq!(
 6039        cx.read_from_clipboard()
 6040            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6041        Some(
 6042            "     for selection in selections.iter() {
 6043            let mut start = selection.start;
 6044            let mut end = selection.end;
 6045            let is_entire_line = selection.is_empty();
 6046            if is_entire_line {
 6047                start = Point::new(start.row, 0);"
 6048                .to_string()
 6049        ),
 6050        "Regular copying for reverse selection works the same",
 6051    );
 6052    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6053    assert_eq!(
 6054        cx.read_from_clipboard()
 6055            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6056        Some(
 6057            "for selection in selections.iter() {
 6058let mut start = selection.start;
 6059let mut end = selection.end;
 6060let is_entire_line = selection.is_empty();
 6061if is_entire_line {
 6062    start = Point::new(start.row, 0);"
 6063                .to_string()
 6064        ),
 6065        "Copying with stripping for reverse selection works the same"
 6066    );
 6067
 6068    cx.set_state(
 6069        r#"            for selection «in selections.iter() {
 6070            let mut start = selection.start;
 6071            let mut end = selection.end;
 6072            let is_entire_line = selection.is_empty();
 6073            if is_entire_line {
 6074                start = Point::new(start.row, 0);ˇ»
 6075                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6076            }
 6077        "#,
 6078    );
 6079    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6080    assert_eq!(
 6081        cx.read_from_clipboard()
 6082            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6083        Some(
 6084            "in selections.iter() {
 6085            let mut start = selection.start;
 6086            let mut end = selection.end;
 6087            let is_entire_line = selection.is_empty();
 6088            if is_entire_line {
 6089                start = Point::new(start.row, 0);"
 6090                .to_string()
 6091        ),
 6092        "When selecting past the indent, the copying works as usual",
 6093    );
 6094    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6095    assert_eq!(
 6096        cx.read_from_clipboard()
 6097            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6098        Some(
 6099            "in selections.iter() {
 6100            let mut start = selection.start;
 6101            let mut end = selection.end;
 6102            let is_entire_line = selection.is_empty();
 6103            if is_entire_line {
 6104                start = Point::new(start.row, 0);"
 6105                .to_string()
 6106        ),
 6107        "When selecting past the indent, nothing is trimmed"
 6108    );
 6109
 6110    cx.set_state(
 6111        r#"            «for selection in selections.iter() {
 6112            let mut start = selection.start;
 6113
 6114            let mut end = selection.end;
 6115            let is_entire_line = selection.is_empty();
 6116            if is_entire_line {
 6117                start = Point::new(start.row, 0);
 6118ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6119            }
 6120        "#,
 6121    );
 6122    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6123    assert_eq!(
 6124        cx.read_from_clipboard()
 6125            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6126        Some(
 6127            "for selection in selections.iter() {
 6128let mut start = selection.start;
 6129
 6130let mut end = selection.end;
 6131let is_entire_line = selection.is_empty();
 6132if is_entire_line {
 6133    start = Point::new(start.row, 0);
 6134"
 6135            .to_string()
 6136        ),
 6137        "Copying with stripping should ignore empty lines"
 6138    );
 6139}
 6140
 6141#[gpui::test]
 6142async fn test_paste_multiline(cx: &mut TestAppContext) {
 6143    init_test(cx, |_| {});
 6144
 6145    let mut cx = EditorTestContext::new(cx).await;
 6146    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6147
 6148    // Cut an indented block, without the leading whitespace.
 6149    cx.set_state(indoc! {"
 6150        const a: B = (
 6151            c(),
 6152            «d(
 6153                e,
 6154                f
 6155            )ˇ»
 6156        );
 6157    "});
 6158    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6159    cx.assert_editor_state(indoc! {"
 6160        const a: B = (
 6161            c(),
 6162            ˇ
 6163        );
 6164    "});
 6165
 6166    // Paste it at the same position.
 6167    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6168    cx.assert_editor_state(indoc! {"
 6169        const a: B = (
 6170            c(),
 6171            d(
 6172                e,
 6173                f
 6174 6175        );
 6176    "});
 6177
 6178    // Paste it at a line with a lower indent level.
 6179    cx.set_state(indoc! {"
 6180        ˇ
 6181        const a: B = (
 6182            c(),
 6183        );
 6184    "});
 6185    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6186    cx.assert_editor_state(indoc! {"
 6187        d(
 6188            e,
 6189            f
 6190 6191        const a: B = (
 6192            c(),
 6193        );
 6194    "});
 6195
 6196    // Cut an indented block, with the leading whitespace.
 6197    cx.set_state(indoc! {"
 6198        const a: B = (
 6199            c(),
 6200        «    d(
 6201                e,
 6202                f
 6203            )
 6204        ˇ»);
 6205    "});
 6206    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6207    cx.assert_editor_state(indoc! {"
 6208        const a: B = (
 6209            c(),
 6210        ˇ);
 6211    "});
 6212
 6213    // Paste it at the same position.
 6214    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6215    cx.assert_editor_state(indoc! {"
 6216        const a: B = (
 6217            c(),
 6218            d(
 6219                e,
 6220                f
 6221            )
 6222        ˇ);
 6223    "});
 6224
 6225    // Paste it at a line with a higher indent level.
 6226    cx.set_state(indoc! {"
 6227        const a: B = (
 6228            c(),
 6229            d(
 6230                e,
 6231 6232            )
 6233        );
 6234    "});
 6235    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6236    cx.assert_editor_state(indoc! {"
 6237        const a: B = (
 6238            c(),
 6239            d(
 6240                e,
 6241                f    d(
 6242                    e,
 6243                    f
 6244                )
 6245        ˇ
 6246            )
 6247        );
 6248    "});
 6249
 6250    // Copy an indented block, starting mid-line
 6251    cx.set_state(indoc! {"
 6252        const a: B = (
 6253            c(),
 6254            somethin«g(
 6255                e,
 6256                f
 6257            )ˇ»
 6258        );
 6259    "});
 6260    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6261
 6262    // Paste it on a line with a lower indent level
 6263    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6264    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6265    cx.assert_editor_state(indoc! {"
 6266        const a: B = (
 6267            c(),
 6268            something(
 6269                e,
 6270                f
 6271            )
 6272        );
 6273        g(
 6274            e,
 6275            f
 6276"});
 6277}
 6278
 6279#[gpui::test]
 6280async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6281    init_test(cx, |_| {});
 6282
 6283    cx.write_to_clipboard(ClipboardItem::new_string(
 6284        "    d(\n        e\n    );\n".into(),
 6285    ));
 6286
 6287    let mut cx = EditorTestContext::new(cx).await;
 6288    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6289
 6290    cx.set_state(indoc! {"
 6291        fn a() {
 6292            b();
 6293            if c() {
 6294                ˇ
 6295            }
 6296        }
 6297    "});
 6298
 6299    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6300    cx.assert_editor_state(indoc! {"
 6301        fn a() {
 6302            b();
 6303            if c() {
 6304                d(
 6305                    e
 6306                );
 6307        ˇ
 6308            }
 6309        }
 6310    "});
 6311
 6312    cx.set_state(indoc! {"
 6313        fn a() {
 6314            b();
 6315            ˇ
 6316        }
 6317    "});
 6318
 6319    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6320    cx.assert_editor_state(indoc! {"
 6321        fn a() {
 6322            b();
 6323            d(
 6324                e
 6325            );
 6326        ˇ
 6327        }
 6328    "});
 6329}
 6330
 6331#[gpui::test]
 6332fn test_select_all(cx: &mut TestAppContext) {
 6333    init_test(cx, |_| {});
 6334
 6335    let editor = cx.add_window(|window, cx| {
 6336        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6337        build_editor(buffer, window, cx)
 6338    });
 6339    _ = editor.update(cx, |editor, window, cx| {
 6340        editor.select_all(&SelectAll, window, cx);
 6341        assert_eq!(
 6342            editor.selections.display_ranges(cx),
 6343            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6344        );
 6345    });
 6346}
 6347
 6348#[gpui::test]
 6349fn test_select_line(cx: &mut TestAppContext) {
 6350    init_test(cx, |_| {});
 6351
 6352    let editor = cx.add_window(|window, cx| {
 6353        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6354        build_editor(buffer, window, cx)
 6355    });
 6356    _ = editor.update(cx, |editor, window, cx| {
 6357        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6358            s.select_display_ranges([
 6359                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6360                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6361                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6362                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6363            ])
 6364        });
 6365        editor.select_line(&SelectLine, window, cx);
 6366        assert_eq!(
 6367            editor.selections.display_ranges(cx),
 6368            vec![
 6369                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6370                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6371            ]
 6372        );
 6373    });
 6374
 6375    _ = editor.update(cx, |editor, window, cx| {
 6376        editor.select_line(&SelectLine, window, cx);
 6377        assert_eq!(
 6378            editor.selections.display_ranges(cx),
 6379            vec![
 6380                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6381                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6382            ]
 6383        );
 6384    });
 6385
 6386    _ = editor.update(cx, |editor, window, cx| {
 6387        editor.select_line(&SelectLine, window, cx);
 6388        assert_eq!(
 6389            editor.selections.display_ranges(cx),
 6390            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6391        );
 6392    });
 6393}
 6394
 6395#[gpui::test]
 6396async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6397    init_test(cx, |_| {});
 6398    let mut cx = EditorTestContext::new(cx).await;
 6399
 6400    #[track_caller]
 6401    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6402        cx.set_state(initial_state);
 6403        cx.update_editor(|e, window, cx| {
 6404            e.split_selection_into_lines(&Default::default(), window, cx)
 6405        });
 6406        cx.assert_editor_state(expected_state);
 6407    }
 6408
 6409    // Selection starts and ends at the middle of lines, left-to-right
 6410    test(
 6411        &mut cx,
 6412        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6413        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6414    );
 6415    // Same thing, right-to-left
 6416    test(
 6417        &mut cx,
 6418        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6419        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6420    );
 6421
 6422    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6423    test(
 6424        &mut cx,
 6425        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6426        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6427    );
 6428    // Same thing, right-to-left
 6429    test(
 6430        &mut cx,
 6431        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6432        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6433    );
 6434
 6435    // Whole buffer, left-to-right, last line ends with newline
 6436    test(
 6437        &mut cx,
 6438        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6439        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6440    );
 6441    // Same thing, right-to-left
 6442    test(
 6443        &mut cx,
 6444        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6445        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6446    );
 6447
 6448    // Starts at the end of a line, ends at the start of another
 6449    test(
 6450        &mut cx,
 6451        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6452        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6453    );
 6454}
 6455
 6456#[gpui::test]
 6457async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6458    init_test(cx, |_| {});
 6459
 6460    let editor = cx.add_window(|window, cx| {
 6461        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6462        build_editor(buffer, window, cx)
 6463    });
 6464
 6465    // setup
 6466    _ = editor.update(cx, |editor, window, cx| {
 6467        editor.fold_creases(
 6468            vec![
 6469                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6470                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6471                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6472            ],
 6473            true,
 6474            window,
 6475            cx,
 6476        );
 6477        assert_eq!(
 6478            editor.display_text(cx),
 6479            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6480        );
 6481    });
 6482
 6483    _ = editor.update(cx, |editor, window, cx| {
 6484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6485            s.select_display_ranges([
 6486                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6487                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6488                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6489                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6490            ])
 6491        });
 6492        editor.split_selection_into_lines(&Default::default(), window, cx);
 6493        assert_eq!(
 6494            editor.display_text(cx),
 6495            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6496        );
 6497    });
 6498    EditorTestContext::for_editor(editor, cx)
 6499        .await
 6500        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6501
 6502    _ = editor.update(cx, |editor, window, cx| {
 6503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6504            s.select_display_ranges([
 6505                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6506            ])
 6507        });
 6508        editor.split_selection_into_lines(&Default::default(), window, cx);
 6509        assert_eq!(
 6510            editor.display_text(cx),
 6511            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6512        );
 6513        assert_eq!(
 6514            editor.selections.display_ranges(cx),
 6515            [
 6516                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6517                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6518                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6519                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6520                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6521                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6522                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6523            ]
 6524        );
 6525    });
 6526    EditorTestContext::for_editor(editor, cx)
 6527        .await
 6528        .assert_editor_state(
 6529            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6530        );
 6531}
 6532
 6533#[gpui::test]
 6534async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6535    init_test(cx, |_| {});
 6536
 6537    let mut cx = EditorTestContext::new(cx).await;
 6538
 6539    cx.set_state(indoc!(
 6540        r#"abc
 6541           defˇghi
 6542
 6543           jk
 6544           nlmo
 6545           "#
 6546    ));
 6547
 6548    cx.update_editor(|editor, window, cx| {
 6549        editor.add_selection_above(&Default::default(), window, cx);
 6550    });
 6551
 6552    cx.assert_editor_state(indoc!(
 6553        r#"abcˇ
 6554           defˇghi
 6555
 6556           jk
 6557           nlmo
 6558           "#
 6559    ));
 6560
 6561    cx.update_editor(|editor, window, cx| {
 6562        editor.add_selection_above(&Default::default(), window, cx);
 6563    });
 6564
 6565    cx.assert_editor_state(indoc!(
 6566        r#"abcˇ
 6567            defˇghi
 6568
 6569            jk
 6570            nlmo
 6571            "#
 6572    ));
 6573
 6574    cx.update_editor(|editor, window, cx| {
 6575        editor.add_selection_below(&Default::default(), window, cx);
 6576    });
 6577
 6578    cx.assert_editor_state(indoc!(
 6579        r#"abc
 6580           defˇghi
 6581
 6582           jk
 6583           nlmo
 6584           "#
 6585    ));
 6586
 6587    cx.update_editor(|editor, window, cx| {
 6588        editor.undo_selection(&Default::default(), window, cx);
 6589    });
 6590
 6591    cx.assert_editor_state(indoc!(
 6592        r#"abcˇ
 6593           defˇghi
 6594
 6595           jk
 6596           nlmo
 6597           "#
 6598    ));
 6599
 6600    cx.update_editor(|editor, window, cx| {
 6601        editor.redo_selection(&Default::default(), window, cx);
 6602    });
 6603
 6604    cx.assert_editor_state(indoc!(
 6605        r#"abc
 6606           defˇghi
 6607
 6608           jk
 6609           nlmo
 6610           "#
 6611    ));
 6612
 6613    cx.update_editor(|editor, window, cx| {
 6614        editor.add_selection_below(&Default::default(), window, cx);
 6615    });
 6616
 6617    cx.assert_editor_state(indoc!(
 6618        r#"abc
 6619           defˇghi
 6620           ˇ
 6621           jk
 6622           nlmo
 6623           "#
 6624    ));
 6625
 6626    cx.update_editor(|editor, window, cx| {
 6627        editor.add_selection_below(&Default::default(), window, cx);
 6628    });
 6629
 6630    cx.assert_editor_state(indoc!(
 6631        r#"abc
 6632           defˇghi
 6633           ˇ
 6634           jkˇ
 6635           nlmo
 6636           "#
 6637    ));
 6638
 6639    cx.update_editor(|editor, window, cx| {
 6640        editor.add_selection_below(&Default::default(), window, cx);
 6641    });
 6642
 6643    cx.assert_editor_state(indoc!(
 6644        r#"abc
 6645           defˇghi
 6646           ˇ
 6647           jkˇ
 6648           nlmˇo
 6649           "#
 6650    ));
 6651
 6652    cx.update_editor(|editor, window, cx| {
 6653        editor.add_selection_below(&Default::default(), window, cx);
 6654    });
 6655
 6656    cx.assert_editor_state(indoc!(
 6657        r#"abc
 6658           defˇghi
 6659           ˇ
 6660           jkˇ
 6661           nlmˇo
 6662           ˇ"#
 6663    ));
 6664
 6665    // change selections
 6666    cx.set_state(indoc!(
 6667        r#"abc
 6668           def«ˇg»hi
 6669
 6670           jk
 6671           nlmo
 6672           "#
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.add_selection_below(&Default::default(), window, cx);
 6677    });
 6678
 6679    cx.assert_editor_state(indoc!(
 6680        r#"abc
 6681           def«ˇg»hi
 6682
 6683           jk
 6684           nlm«ˇo»
 6685           "#
 6686    ));
 6687
 6688    cx.update_editor(|editor, window, cx| {
 6689        editor.add_selection_below(&Default::default(), window, cx);
 6690    });
 6691
 6692    cx.assert_editor_state(indoc!(
 6693        r#"abc
 6694           def«ˇg»hi
 6695
 6696           jk
 6697           nlm«ˇo»
 6698           "#
 6699    ));
 6700
 6701    cx.update_editor(|editor, window, cx| {
 6702        editor.add_selection_above(&Default::default(), window, cx);
 6703    });
 6704
 6705    cx.assert_editor_state(indoc!(
 6706        r#"abc
 6707           def«ˇg»hi
 6708
 6709           jk
 6710           nlmo
 6711           "#
 6712    ));
 6713
 6714    cx.update_editor(|editor, window, cx| {
 6715        editor.add_selection_above(&Default::default(), window, cx);
 6716    });
 6717
 6718    cx.assert_editor_state(indoc!(
 6719        r#"abc
 6720           def«ˇg»hi
 6721
 6722           jk
 6723           nlmo
 6724           "#
 6725    ));
 6726
 6727    // Change selections again
 6728    cx.set_state(indoc!(
 6729        r#"a«bc
 6730           defgˇ»hi
 6731
 6732           jk
 6733           nlmo
 6734           "#
 6735    ));
 6736
 6737    cx.update_editor(|editor, window, cx| {
 6738        editor.add_selection_below(&Default::default(), window, cx);
 6739    });
 6740
 6741    cx.assert_editor_state(indoc!(
 6742        r#"a«bcˇ»
 6743           d«efgˇ»hi
 6744
 6745           j«kˇ»
 6746           nlmo
 6747           "#
 6748    ));
 6749
 6750    cx.update_editor(|editor, window, cx| {
 6751        editor.add_selection_below(&Default::default(), window, cx);
 6752    });
 6753    cx.assert_editor_state(indoc!(
 6754        r#"a«bcˇ»
 6755           d«efgˇ»hi
 6756
 6757           j«kˇ»
 6758           n«lmoˇ»
 6759           "#
 6760    ));
 6761    cx.update_editor(|editor, window, cx| {
 6762        editor.add_selection_above(&Default::default(), window, cx);
 6763    });
 6764
 6765    cx.assert_editor_state(indoc!(
 6766        r#"a«bcˇ»
 6767           d«efgˇ»hi
 6768
 6769           j«kˇ»
 6770           nlmo
 6771           "#
 6772    ));
 6773
 6774    // Change selections again
 6775    cx.set_state(indoc!(
 6776        r#"abc
 6777           d«ˇefghi
 6778
 6779           jk
 6780           nlm»o
 6781           "#
 6782    ));
 6783
 6784    cx.update_editor(|editor, window, cx| {
 6785        editor.add_selection_above(&Default::default(), window, cx);
 6786    });
 6787
 6788    cx.assert_editor_state(indoc!(
 6789        r#"a«ˇbc»
 6790           d«ˇef»ghi
 6791
 6792           j«ˇk»
 6793           n«ˇlm»o
 6794           "#
 6795    ));
 6796
 6797    cx.update_editor(|editor, window, cx| {
 6798        editor.add_selection_below(&Default::default(), window, cx);
 6799    });
 6800
 6801    cx.assert_editor_state(indoc!(
 6802        r#"abc
 6803           d«ˇef»ghi
 6804
 6805           j«ˇk»
 6806           n«ˇlm»o
 6807           "#
 6808    ));
 6809}
 6810
 6811#[gpui::test]
 6812async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6813    init_test(cx, |_| {});
 6814    let mut cx = EditorTestContext::new(cx).await;
 6815
 6816    cx.set_state(indoc!(
 6817        r#"line onˇe
 6818           liˇne two
 6819           line three
 6820           line four"#
 6821    ));
 6822
 6823    cx.update_editor(|editor, window, cx| {
 6824        editor.add_selection_below(&Default::default(), window, cx);
 6825    });
 6826
 6827    // test multiple cursors expand in the same direction
 6828    cx.assert_editor_state(indoc!(
 6829        r#"line onˇe
 6830           liˇne twˇo
 6831           liˇne three
 6832           line four"#
 6833    ));
 6834
 6835    cx.update_editor(|editor, window, cx| {
 6836        editor.add_selection_below(&Default::default(), window, cx);
 6837    });
 6838
 6839    cx.update_editor(|editor, window, cx| {
 6840        editor.add_selection_below(&Default::default(), window, cx);
 6841    });
 6842
 6843    // test multiple cursors expand below overflow
 6844    cx.assert_editor_state(indoc!(
 6845        r#"line onˇe
 6846           liˇne twˇo
 6847           liˇne thˇree
 6848           liˇne foˇur"#
 6849    ));
 6850
 6851    cx.update_editor(|editor, window, cx| {
 6852        editor.add_selection_above(&Default::default(), window, cx);
 6853    });
 6854
 6855    // test multiple cursors retrieves back correctly
 6856    cx.assert_editor_state(indoc!(
 6857        r#"line onˇe
 6858           liˇne twˇo
 6859           liˇne thˇree
 6860           line four"#
 6861    ));
 6862
 6863    cx.update_editor(|editor, window, cx| {
 6864        editor.add_selection_above(&Default::default(), window, cx);
 6865    });
 6866
 6867    cx.update_editor(|editor, window, cx| {
 6868        editor.add_selection_above(&Default::default(), window, cx);
 6869    });
 6870
 6871    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6872    cx.assert_editor_state(indoc!(
 6873        r#"liˇne onˇe
 6874           liˇne two
 6875           line three
 6876           line four"#
 6877    ));
 6878
 6879    cx.update_editor(|editor, window, cx| {
 6880        editor.undo_selection(&Default::default(), window, cx);
 6881    });
 6882
 6883    // test undo
 6884    cx.assert_editor_state(indoc!(
 6885        r#"line onˇe
 6886           liˇne twˇo
 6887           line three
 6888           line four"#
 6889    ));
 6890
 6891    cx.update_editor(|editor, window, cx| {
 6892        editor.redo_selection(&Default::default(), window, cx);
 6893    });
 6894
 6895    // test redo
 6896    cx.assert_editor_state(indoc!(
 6897        r#"liˇne onˇe
 6898           liˇne two
 6899           line three
 6900           line four"#
 6901    ));
 6902
 6903    cx.set_state(indoc!(
 6904        r#"abcd
 6905           ef«ghˇ»
 6906           ijkl
 6907           «mˇ»nop"#
 6908    ));
 6909
 6910    cx.update_editor(|editor, window, cx| {
 6911        editor.add_selection_above(&Default::default(), window, cx);
 6912    });
 6913
 6914    // test multiple selections expand in the same direction
 6915    cx.assert_editor_state(indoc!(
 6916        r#"ab«cdˇ»
 6917           ef«ghˇ»
 6918           «iˇ»jkl
 6919           «mˇ»nop"#
 6920    ));
 6921
 6922    cx.update_editor(|editor, window, cx| {
 6923        editor.add_selection_above(&Default::default(), window, cx);
 6924    });
 6925
 6926    // test multiple selection upward overflow
 6927    cx.assert_editor_state(indoc!(
 6928        r#"ab«cdˇ»
 6929           «eˇ»f«ghˇ»
 6930           «iˇ»jkl
 6931           «mˇ»nop"#
 6932    ));
 6933
 6934    cx.update_editor(|editor, window, cx| {
 6935        editor.add_selection_below(&Default::default(), window, cx);
 6936    });
 6937
 6938    // test multiple selection retrieves back correctly
 6939    cx.assert_editor_state(indoc!(
 6940        r#"abcd
 6941           ef«ghˇ»
 6942           «iˇ»jkl
 6943           «mˇ»nop"#
 6944    ));
 6945
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.add_selection_below(&Default::default(), window, cx);
 6948    });
 6949
 6950    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6951    cx.assert_editor_state(indoc!(
 6952        r#"abcd
 6953           ef«ghˇ»
 6954           ij«klˇ»
 6955           «mˇ»nop"#
 6956    ));
 6957
 6958    cx.update_editor(|editor, window, cx| {
 6959        editor.undo_selection(&Default::default(), window, cx);
 6960    });
 6961
 6962    // test undo
 6963    cx.assert_editor_state(indoc!(
 6964        r#"abcd
 6965           ef«ghˇ»
 6966           «iˇ»jkl
 6967           «mˇ»nop"#
 6968    ));
 6969
 6970    cx.update_editor(|editor, window, cx| {
 6971        editor.redo_selection(&Default::default(), window, cx);
 6972    });
 6973
 6974    // test redo
 6975    cx.assert_editor_state(indoc!(
 6976        r#"abcd
 6977           ef«ghˇ»
 6978           ij«klˇ»
 6979           «mˇ»nop"#
 6980    ));
 6981}
 6982
 6983#[gpui::test]
 6984async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6985    init_test(cx, |_| {});
 6986    let mut cx = EditorTestContext::new(cx).await;
 6987
 6988    cx.set_state(indoc!(
 6989        r#"line onˇe
 6990           liˇne two
 6991           line three
 6992           line four"#
 6993    ));
 6994
 6995    cx.update_editor(|editor, window, cx| {
 6996        editor.add_selection_below(&Default::default(), window, cx);
 6997        editor.add_selection_below(&Default::default(), window, cx);
 6998        editor.add_selection_below(&Default::default(), window, cx);
 6999    });
 7000
 7001    // initial state with two multi cursor groups
 7002    cx.assert_editor_state(indoc!(
 7003        r#"line onˇe
 7004           liˇne twˇo
 7005           liˇne thˇree
 7006           liˇne foˇur"#
 7007    ));
 7008
 7009    // add single cursor in middle - simulate opt click
 7010    cx.update_editor(|editor, window, cx| {
 7011        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7012        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7013        editor.end_selection(window, cx);
 7014    });
 7015
 7016    cx.assert_editor_state(indoc!(
 7017        r#"line onˇe
 7018           liˇne twˇo
 7019           liˇneˇ thˇree
 7020           liˇne foˇur"#
 7021    ));
 7022
 7023    cx.update_editor(|editor, window, cx| {
 7024        editor.add_selection_above(&Default::default(), window, cx);
 7025    });
 7026
 7027    // test new added selection expands above and existing selection shrinks
 7028    cx.assert_editor_state(indoc!(
 7029        r#"line onˇe
 7030           liˇneˇ twˇo
 7031           liˇneˇ thˇree
 7032           line four"#
 7033    ));
 7034
 7035    cx.update_editor(|editor, window, cx| {
 7036        editor.add_selection_above(&Default::default(), window, cx);
 7037    });
 7038
 7039    // test new added selection expands above and existing selection shrinks
 7040    cx.assert_editor_state(indoc!(
 7041        r#"lineˇ onˇe
 7042           liˇneˇ twˇo
 7043           lineˇ three
 7044           line four"#
 7045    ));
 7046
 7047    // intial state with two selection groups
 7048    cx.set_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           ijkl
 7052           «mˇ»nop"#
 7053    ));
 7054
 7055    cx.update_editor(|editor, window, cx| {
 7056        editor.add_selection_above(&Default::default(), window, cx);
 7057        editor.add_selection_above(&Default::default(), window, cx);
 7058    });
 7059
 7060    cx.assert_editor_state(indoc!(
 7061        r#"ab«cdˇ»
 7062           «eˇ»f«ghˇ»
 7063           «iˇ»jkl
 7064           «mˇ»nop"#
 7065    ));
 7066
 7067    // add single selection in middle - simulate opt drag
 7068    cx.update_editor(|editor, window, cx| {
 7069        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7070        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7071        editor.update_selection(
 7072            DisplayPoint::new(DisplayRow(2), 4),
 7073            0,
 7074            gpui::Point::<f32>::default(),
 7075            window,
 7076            cx,
 7077        );
 7078        editor.end_selection(window, cx);
 7079    });
 7080
 7081    cx.assert_editor_state(indoc!(
 7082        r#"ab«cdˇ»
 7083           «eˇ»f«ghˇ»
 7084           «iˇ»jk«lˇ»
 7085           «mˇ»nop"#
 7086    ));
 7087
 7088    cx.update_editor(|editor, window, cx| {
 7089        editor.add_selection_below(&Default::default(), window, cx);
 7090    });
 7091
 7092    // test new added selection expands below, others shrinks from above
 7093    cx.assert_editor_state(indoc!(
 7094        r#"abcd
 7095           ef«ghˇ»
 7096           «iˇ»jk«lˇ»
 7097           «mˇ»no«pˇ»"#
 7098    ));
 7099}
 7100
 7101#[gpui::test]
 7102async fn test_select_next(cx: &mut TestAppContext) {
 7103    init_test(cx, |_| {});
 7104
 7105    let mut cx = EditorTestContext::new(cx).await;
 7106    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7107
 7108    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7109        .unwrap();
 7110    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7111
 7112    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7113        .unwrap();
 7114    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7115
 7116    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7117    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7118
 7119    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7120    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7121
 7122    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7123        .unwrap();
 7124    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7125
 7126    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7127        .unwrap();
 7128    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7129
 7130    // Test selection direction should be preserved
 7131    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7132
 7133    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7134        .unwrap();
 7135    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7136}
 7137
 7138#[gpui::test]
 7139async fn test_select_all_matches(cx: &mut TestAppContext) {
 7140    init_test(cx, |_| {});
 7141
 7142    let mut cx = EditorTestContext::new(cx).await;
 7143
 7144    // Test caret-only selections
 7145    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7146    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7147        .unwrap();
 7148    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7149
 7150    // Test left-to-right selections
 7151    cx.set_state("abc\n«abcˇ»\nabc");
 7152    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7153        .unwrap();
 7154    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7155
 7156    // Test right-to-left selections
 7157    cx.set_state("abc\n«ˇabc»\nabc");
 7158    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7159        .unwrap();
 7160    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7161
 7162    // Test selecting whitespace with caret selection
 7163    cx.set_state("abc\nˇ   abc\nabc");
 7164    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7165        .unwrap();
 7166    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7167
 7168    // Test selecting whitespace with left-to-right selection
 7169    cx.set_state("abc\n«ˇ  »abc\nabc");
 7170    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7171        .unwrap();
 7172    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7173
 7174    // Test no matches with right-to-left selection
 7175    cx.set_state("abc\n«  ˇ»abc\nabc");
 7176    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7177        .unwrap();
 7178    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7179
 7180    // Test with a single word and clip_at_line_ends=true (#29823)
 7181    cx.set_state("aˇbc");
 7182    cx.update_editor(|e, window, cx| {
 7183        e.set_clip_at_line_ends(true, cx);
 7184        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7185        e.set_clip_at_line_ends(false, cx);
 7186    });
 7187    cx.assert_editor_state("«abcˇ»");
 7188}
 7189
 7190#[gpui::test]
 7191async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7192    init_test(cx, |_| {});
 7193
 7194    let mut cx = EditorTestContext::new(cx).await;
 7195
 7196    let large_body_1 = "\nd".repeat(200);
 7197    let large_body_2 = "\ne".repeat(200);
 7198
 7199    cx.set_state(&format!(
 7200        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7201    ));
 7202    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7203        let scroll_position = editor.scroll_position(cx);
 7204        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7205        scroll_position
 7206    });
 7207
 7208    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7209        .unwrap();
 7210    cx.assert_editor_state(&format!(
 7211        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7212    ));
 7213    let scroll_position_after_selection =
 7214        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7215    assert_eq!(
 7216        initial_scroll_position, scroll_position_after_selection,
 7217        "Scroll position should not change after selecting all matches"
 7218    );
 7219}
 7220
 7221#[gpui::test]
 7222async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7223    init_test(cx, |_| {});
 7224
 7225    let mut cx = EditorLspTestContext::new_rust(
 7226        lsp::ServerCapabilities {
 7227            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7228            ..Default::default()
 7229        },
 7230        cx,
 7231    )
 7232    .await;
 7233
 7234    cx.set_state(indoc! {"
 7235        line 1
 7236        line 2
 7237        linˇe 3
 7238        line 4
 7239        line 5
 7240    "});
 7241
 7242    // Make an edit
 7243    cx.update_editor(|editor, window, cx| {
 7244        editor.handle_input("X", window, cx);
 7245    });
 7246
 7247    // Move cursor to a different position
 7248    cx.update_editor(|editor, window, cx| {
 7249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7250            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7251        });
 7252    });
 7253
 7254    cx.assert_editor_state(indoc! {"
 7255        line 1
 7256        line 2
 7257        linXe 3
 7258        line 4
 7259        liˇne 5
 7260    "});
 7261
 7262    cx.lsp
 7263        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7264            Ok(Some(vec![lsp::TextEdit::new(
 7265                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7266                "PREFIX ".to_string(),
 7267            )]))
 7268        });
 7269
 7270    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7271        .unwrap()
 7272        .await
 7273        .unwrap();
 7274
 7275    cx.assert_editor_state(indoc! {"
 7276        PREFIX line 1
 7277        line 2
 7278        linXe 3
 7279        line 4
 7280        liˇne 5
 7281    "});
 7282
 7283    // Undo formatting
 7284    cx.update_editor(|editor, window, cx| {
 7285        editor.undo(&Default::default(), window, cx);
 7286    });
 7287
 7288    // Verify cursor moved back to position after edit
 7289    cx.assert_editor_state(indoc! {"
 7290        line 1
 7291        line 2
 7292        linXˇe 3
 7293        line 4
 7294        line 5
 7295    "});
 7296}
 7297
 7298#[gpui::test]
 7299async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7300    init_test(cx, |_| {});
 7301
 7302    let mut cx = EditorTestContext::new(cx).await;
 7303
 7304    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7305    cx.update_editor(|editor, window, cx| {
 7306        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7307    });
 7308
 7309    cx.set_state(indoc! {"
 7310        line 1
 7311        line 2
 7312        linˇe 3
 7313        line 4
 7314        line 5
 7315        line 6
 7316        line 7
 7317        line 8
 7318        line 9
 7319        line 10
 7320    "});
 7321
 7322    let snapshot = cx.buffer_snapshot();
 7323    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7324
 7325    cx.update(|_, cx| {
 7326        provider.update(cx, |provider, _| {
 7327            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7328                id: None,
 7329                edits: vec![(edit_position..edit_position, "X".into())],
 7330                edit_preview: None,
 7331            }))
 7332        })
 7333    });
 7334
 7335    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7336    cx.update_editor(|editor, window, cx| {
 7337        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7338    });
 7339
 7340    cx.assert_editor_state(indoc! {"
 7341        line 1
 7342        line 2
 7343        lineXˇ 3
 7344        line 4
 7345        line 5
 7346        line 6
 7347        line 7
 7348        line 8
 7349        line 9
 7350        line 10
 7351    "});
 7352
 7353    cx.update_editor(|editor, window, cx| {
 7354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7355            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7356        });
 7357    });
 7358
 7359    cx.assert_editor_state(indoc! {"
 7360        line 1
 7361        line 2
 7362        lineX 3
 7363        line 4
 7364        line 5
 7365        line 6
 7366        line 7
 7367        line 8
 7368        line 9
 7369        liˇne 10
 7370    "});
 7371
 7372    cx.update_editor(|editor, window, cx| {
 7373        editor.undo(&Default::default(), window, cx);
 7374    });
 7375
 7376    cx.assert_editor_state(indoc! {"
 7377        line 1
 7378        line 2
 7379        lineˇ 3
 7380        line 4
 7381        line 5
 7382        line 6
 7383        line 7
 7384        line 8
 7385        line 9
 7386        line 10
 7387    "});
 7388}
 7389
 7390#[gpui::test]
 7391async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7392    init_test(cx, |_| {});
 7393
 7394    let mut cx = EditorTestContext::new(cx).await;
 7395    cx.set_state(
 7396        r#"let foo = 2;
 7397lˇet foo = 2;
 7398let fooˇ = 2;
 7399let foo = 2;
 7400let foo = ˇ2;"#,
 7401    );
 7402
 7403    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7404        .unwrap();
 7405    cx.assert_editor_state(
 7406        r#"let foo = 2;
 7407«letˇ» foo = 2;
 7408let «fooˇ» = 2;
 7409let foo = 2;
 7410let foo = «2ˇ»;"#,
 7411    );
 7412
 7413    // noop for multiple selections with different contents
 7414    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7415        .unwrap();
 7416    cx.assert_editor_state(
 7417        r#"let foo = 2;
 7418«letˇ» foo = 2;
 7419let «fooˇ» = 2;
 7420let foo = 2;
 7421let foo = «2ˇ»;"#,
 7422    );
 7423
 7424    // Test last selection direction should be preserved
 7425    cx.set_state(
 7426        r#"let foo = 2;
 7427let foo = 2;
 7428let «fooˇ» = 2;
 7429let «ˇfoo» = 2;
 7430let foo = 2;"#,
 7431    );
 7432
 7433    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7434        .unwrap();
 7435    cx.assert_editor_state(
 7436        r#"let foo = 2;
 7437let foo = 2;
 7438let «fooˇ» = 2;
 7439let «ˇfoo» = 2;
 7440let «ˇfoo» = 2;"#,
 7441    );
 7442}
 7443
 7444#[gpui::test]
 7445async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7446    init_test(cx, |_| {});
 7447
 7448    let mut cx =
 7449        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7450
 7451    cx.assert_editor_state(indoc! {"
 7452        ˇbbb
 7453        ccc
 7454
 7455        bbb
 7456        ccc
 7457        "});
 7458    cx.dispatch_action(SelectPrevious::default());
 7459    cx.assert_editor_state(indoc! {"
 7460                «bbbˇ»
 7461                ccc
 7462
 7463                bbb
 7464                ccc
 7465                "});
 7466    cx.dispatch_action(SelectPrevious::default());
 7467    cx.assert_editor_state(indoc! {"
 7468                «bbbˇ»
 7469                ccc
 7470
 7471                «bbbˇ»
 7472                ccc
 7473                "});
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7482
 7483    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7484        .unwrap();
 7485    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7486
 7487    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7488        .unwrap();
 7489    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7490
 7491    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7492    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7493
 7494    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7495    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7496
 7497    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7498        .unwrap();
 7499    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7500
 7501    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7502        .unwrap();
 7503    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7504}
 7505
 7506#[gpui::test]
 7507async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7508    init_test(cx, |_| {});
 7509
 7510    let mut cx = EditorTestContext::new(cx).await;
 7511    cx.set_state("");
 7512
 7513    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7514        .unwrap();
 7515    cx.assert_editor_state("«aˇ»");
 7516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7517        .unwrap();
 7518    cx.assert_editor_state("«aˇ»");
 7519}
 7520
 7521#[gpui::test]
 7522async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7523    init_test(cx, |_| {});
 7524
 7525    let mut cx = EditorTestContext::new(cx).await;
 7526    cx.set_state(
 7527        r#"let foo = 2;
 7528lˇet foo = 2;
 7529let fooˇ = 2;
 7530let foo = 2;
 7531let foo = ˇ2;"#,
 7532    );
 7533
 7534    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7535        .unwrap();
 7536    cx.assert_editor_state(
 7537        r#"let foo = 2;
 7538«letˇ» foo = 2;
 7539let «fooˇ» = 2;
 7540let foo = 2;
 7541let foo = «2ˇ»;"#,
 7542    );
 7543
 7544    // noop for multiple selections with different contents
 7545    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7546        .unwrap();
 7547    cx.assert_editor_state(
 7548        r#"let foo = 2;
 7549«letˇ» foo = 2;
 7550let «fooˇ» = 2;
 7551let foo = 2;
 7552let foo = «2ˇ»;"#,
 7553    );
 7554}
 7555
 7556#[gpui::test]
 7557async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7558    init_test(cx, |_| {});
 7559
 7560    let mut cx = EditorTestContext::new(cx).await;
 7561    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7562
 7563    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7564        .unwrap();
 7565    // selection direction is preserved
 7566    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7567
 7568    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7569        .unwrap();
 7570    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7571
 7572    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7573    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7574
 7575    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7576    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7577
 7578    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7579        .unwrap();
 7580    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7581
 7582    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7583        .unwrap();
 7584    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7585}
 7586
 7587#[gpui::test]
 7588async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7589    init_test(cx, |_| {});
 7590
 7591    let language = Arc::new(Language::new(
 7592        LanguageConfig::default(),
 7593        Some(tree_sitter_rust::LANGUAGE.into()),
 7594    ));
 7595
 7596    let text = r#"
 7597        use mod1::mod2::{mod3, mod4};
 7598
 7599        fn fn_1(param1: bool, param2: &str) {
 7600            let var1 = "text";
 7601        }
 7602    "#
 7603    .unindent();
 7604
 7605    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7606    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7607    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7608
 7609    editor
 7610        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7611        .await;
 7612
 7613    editor.update_in(cx, |editor, window, cx| {
 7614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7615            s.select_display_ranges([
 7616                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7617                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7618                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7619            ]);
 7620        });
 7621        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7622    });
 7623    editor.update(cx, |editor, cx| {
 7624        assert_text_with_selections(
 7625            editor,
 7626            indoc! {r#"
 7627                use mod1::mod2::{mod3, «mod4ˇ»};
 7628
 7629                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7630                    let var1 = "«ˇtext»";
 7631                }
 7632            "#},
 7633            cx,
 7634        );
 7635    });
 7636
 7637    editor.update_in(cx, |editor, window, cx| {
 7638        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7639    });
 7640    editor.update(cx, |editor, cx| {
 7641        assert_text_with_selections(
 7642            editor,
 7643            indoc! {r#"
 7644                use mod1::mod2::«{mod3, mod4}ˇ»;
 7645
 7646                «ˇfn fn_1(param1: bool, param2: &str) {
 7647                    let var1 = "text";
 7648 7649            "#},
 7650            cx,
 7651        );
 7652    });
 7653
 7654    editor.update_in(cx, |editor, window, cx| {
 7655        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7656    });
 7657    assert_eq!(
 7658        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7659        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7660    );
 7661
 7662    // Trying to expand the selected syntax node one more time has no effect.
 7663    editor.update_in(cx, |editor, window, cx| {
 7664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7665    });
 7666    assert_eq!(
 7667        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7668        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7669    );
 7670
 7671    editor.update_in(cx, |editor, window, cx| {
 7672        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7673    });
 7674    editor.update(cx, |editor, cx| {
 7675        assert_text_with_selections(
 7676            editor,
 7677            indoc! {r#"
 7678                use mod1::mod2::«{mod3, mod4}ˇ»;
 7679
 7680                «ˇfn fn_1(param1: bool, param2: &str) {
 7681                    let var1 = "text";
 7682 7683            "#},
 7684            cx,
 7685        );
 7686    });
 7687
 7688    editor.update_in(cx, |editor, window, cx| {
 7689        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7690    });
 7691    editor.update(cx, |editor, cx| {
 7692        assert_text_with_selections(
 7693            editor,
 7694            indoc! {r#"
 7695                use mod1::mod2::{mod3, «mod4ˇ»};
 7696
 7697                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7698                    let var1 = "«ˇtext»";
 7699                }
 7700            "#},
 7701            cx,
 7702        );
 7703    });
 7704
 7705    editor.update_in(cx, |editor, window, cx| {
 7706        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7707    });
 7708    editor.update(cx, |editor, cx| {
 7709        assert_text_with_selections(
 7710            editor,
 7711            indoc! {r#"
 7712                use mod1::mod2::{mod3, mo«ˇ»d4};
 7713
 7714                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7715                    let var1 = "te«ˇ»xt";
 7716                }
 7717            "#},
 7718            cx,
 7719        );
 7720    });
 7721
 7722    // Trying to shrink the selected syntax node one more time has no effect.
 7723    editor.update_in(cx, |editor, window, cx| {
 7724        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7725    });
 7726    editor.update_in(cx, |editor, _, cx| {
 7727        assert_text_with_selections(
 7728            editor,
 7729            indoc! {r#"
 7730                use mod1::mod2::{mod3, mo«ˇ»d4};
 7731
 7732                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7733                    let var1 = "te«ˇ»xt";
 7734                }
 7735            "#},
 7736            cx,
 7737        );
 7738    });
 7739
 7740    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7741    // a fold.
 7742    editor.update_in(cx, |editor, window, cx| {
 7743        editor.fold_creases(
 7744            vec![
 7745                Crease::simple(
 7746                    Point::new(0, 21)..Point::new(0, 24),
 7747                    FoldPlaceholder::test(),
 7748                ),
 7749                Crease::simple(
 7750                    Point::new(3, 20)..Point::new(3, 22),
 7751                    FoldPlaceholder::test(),
 7752                ),
 7753            ],
 7754            true,
 7755            window,
 7756            cx,
 7757        );
 7758        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7759    });
 7760    editor.update(cx, |editor, cx| {
 7761        assert_text_with_selections(
 7762            editor,
 7763            indoc! {r#"
 7764                use mod1::mod2::«{mod3, mod4}ˇ»;
 7765
 7766                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7767                    let var1 = "«ˇtext»";
 7768                }
 7769            "#},
 7770            cx,
 7771        );
 7772    });
 7773}
 7774
 7775#[gpui::test]
 7776async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7777    init_test(cx, |_| {});
 7778
 7779    let language = Arc::new(Language::new(
 7780        LanguageConfig::default(),
 7781        Some(tree_sitter_rust::LANGUAGE.into()),
 7782    ));
 7783
 7784    let text = "let a = 2;";
 7785
 7786    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7788    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7789
 7790    editor
 7791        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7792        .await;
 7793
 7794    // Test case 1: Cursor at end of word
 7795    editor.update_in(cx, |editor, window, cx| {
 7796        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7797            s.select_display_ranges([
 7798                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7799            ]);
 7800        });
 7801    });
 7802    editor.update(cx, |editor, cx| {
 7803        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7804    });
 7805    editor.update_in(cx, |editor, window, cx| {
 7806        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7807    });
 7808    editor.update(cx, |editor, cx| {
 7809        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7810    });
 7811    editor.update_in(cx, |editor, window, cx| {
 7812        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7813    });
 7814    editor.update(cx, |editor, cx| {
 7815        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7816    });
 7817
 7818    // Test case 2: Cursor at end of statement
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update(cx, |editor, cx| {
 7827        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7828    });
 7829    editor.update_in(cx, |editor, window, cx| {
 7830        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7831    });
 7832    editor.update(cx, |editor, cx| {
 7833        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7834    });
 7835}
 7836
 7837#[gpui::test]
 7838async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7839    init_test(cx, |_| {});
 7840
 7841    let language = Arc::new(Language::new(
 7842        LanguageConfig::default(),
 7843        Some(tree_sitter_rust::LANGUAGE.into()),
 7844    ));
 7845
 7846    let text = r#"
 7847        use mod1::mod2::{mod3, mod4};
 7848
 7849        fn fn_1(param1: bool, param2: &str) {
 7850            let var1 = "hello world";
 7851        }
 7852    "#
 7853    .unindent();
 7854
 7855    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7856    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7857    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7858
 7859    editor
 7860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7861        .await;
 7862
 7863    // Test 1: Cursor on a letter of a string word
 7864    editor.update_in(cx, |editor, window, cx| {
 7865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7866            s.select_display_ranges([
 7867                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7868            ]);
 7869        });
 7870    });
 7871    editor.update_in(cx, |editor, window, cx| {
 7872        assert_text_with_selections(
 7873            editor,
 7874            indoc! {r#"
 7875                use mod1::mod2::{mod3, mod4};
 7876
 7877                fn fn_1(param1: bool, param2: &str) {
 7878                    let var1 = "hˇello world";
 7879                }
 7880            "#},
 7881            cx,
 7882        );
 7883        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7884        assert_text_with_selections(
 7885            editor,
 7886            indoc! {r#"
 7887                use mod1::mod2::{mod3, mod4};
 7888
 7889                fn fn_1(param1: bool, param2: &str) {
 7890                    let var1 = "«ˇhello» world";
 7891                }
 7892            "#},
 7893            cx,
 7894        );
 7895    });
 7896
 7897    // Test 2: Partial selection within a word
 7898    editor.update_in(cx, |editor, window, cx| {
 7899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7900            s.select_display_ranges([
 7901                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7902            ]);
 7903        });
 7904    });
 7905    editor.update_in(cx, |editor, window, cx| {
 7906        assert_text_with_selections(
 7907            editor,
 7908            indoc! {r#"
 7909                use mod1::mod2::{mod3, mod4};
 7910
 7911                fn fn_1(param1: bool, param2: &str) {
 7912                    let var1 = "h«elˇ»lo world";
 7913                }
 7914            "#},
 7915            cx,
 7916        );
 7917        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7918        assert_text_with_selections(
 7919            editor,
 7920            indoc! {r#"
 7921                use mod1::mod2::{mod3, mod4};
 7922
 7923                fn fn_1(param1: bool, param2: &str) {
 7924                    let var1 = "«ˇhello» world";
 7925                }
 7926            "#},
 7927            cx,
 7928        );
 7929    });
 7930
 7931    // Test 3: Complete word already selected
 7932    editor.update_in(cx, |editor, window, cx| {
 7933        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7934            s.select_display_ranges([
 7935                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7936            ]);
 7937        });
 7938    });
 7939    editor.update_in(cx, |editor, window, cx| {
 7940        assert_text_with_selections(
 7941            editor,
 7942            indoc! {r#"
 7943                use mod1::mod2::{mod3, mod4};
 7944
 7945                fn fn_1(param1: bool, param2: &str) {
 7946                    let var1 = "«helloˇ» world";
 7947                }
 7948            "#},
 7949            cx,
 7950        );
 7951        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7952        assert_text_with_selections(
 7953            editor,
 7954            indoc! {r#"
 7955                use mod1::mod2::{mod3, mod4};
 7956
 7957                fn fn_1(param1: bool, param2: &str) {
 7958                    let var1 = "«hello worldˇ»";
 7959                }
 7960            "#},
 7961            cx,
 7962        );
 7963    });
 7964
 7965    // Test 4: Selection spanning across words
 7966    editor.update_in(cx, |editor, window, cx| {
 7967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7968            s.select_display_ranges([
 7969                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7970            ]);
 7971        });
 7972    });
 7973    editor.update_in(cx, |editor, window, cx| {
 7974        assert_text_with_selections(
 7975            editor,
 7976            indoc! {r#"
 7977                use mod1::mod2::{mod3, mod4};
 7978
 7979                fn fn_1(param1: bool, param2: &str) {
 7980                    let var1 = "hel«lo woˇ»rld";
 7981                }
 7982            "#},
 7983            cx,
 7984        );
 7985        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7986        assert_text_with_selections(
 7987            editor,
 7988            indoc! {r#"
 7989                use mod1::mod2::{mod3, mod4};
 7990
 7991                fn fn_1(param1: bool, param2: &str) {
 7992                    let var1 = "«ˇhello world»";
 7993                }
 7994            "#},
 7995            cx,
 7996        );
 7997    });
 7998
 7999    // Test 5: Expansion beyond string
 8000    editor.update_in(cx, |editor, window, cx| {
 8001        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8002        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8003        assert_text_with_selections(
 8004            editor,
 8005            indoc! {r#"
 8006                use mod1::mod2::{mod3, mod4};
 8007
 8008                fn fn_1(param1: bool, param2: &str) {
 8009                    «ˇlet var1 = "hello world";»
 8010                }
 8011            "#},
 8012            cx,
 8013        );
 8014    });
 8015}
 8016
 8017#[gpui::test]
 8018async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) {
 8019    init_test(cx, |_| {});
 8020
 8021    let mut cx = EditorTestContext::new(cx).await;
 8022
 8023    let language = Arc::new(Language::new(
 8024        LanguageConfig::default(),
 8025        Some(tree_sitter_rust::LANGUAGE.into()),
 8026    ));
 8027
 8028    cx.update_buffer(|buffer, cx| {
 8029        buffer.set_language(Some(language), cx);
 8030    });
 8031
 8032    cx.set_state(
 8033        &r#"
 8034            use mod1::mod2::{«mod3ˇ», mod4};
 8035        "#
 8036        .unindent(),
 8037    );
 8038    cx.update_editor(|editor, window, cx| {
 8039        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8040    });
 8041    cx.assert_editor_state(
 8042        &r#"
 8043            use mod1::mod2::«mod3ˇ»;
 8044        "#
 8045        .unindent(),
 8046    );
 8047}
 8048
 8049#[gpui::test]
 8050async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8051    init_test(cx, |_| {});
 8052
 8053    let base_text = r#"
 8054        impl A {
 8055            // this is an uncommitted comment
 8056
 8057            fn b() {
 8058                c();
 8059            }
 8060
 8061            // this is another uncommitted comment
 8062
 8063            fn d() {
 8064                // e
 8065                // f
 8066            }
 8067        }
 8068
 8069        fn g() {
 8070            // h
 8071        }
 8072    "#
 8073    .unindent();
 8074
 8075    let text = r#"
 8076        ˇimpl A {
 8077
 8078            fn b() {
 8079                c();
 8080            }
 8081
 8082            fn d() {
 8083                // e
 8084                // f
 8085            }
 8086        }
 8087
 8088        fn g() {
 8089            // h
 8090        }
 8091    "#
 8092    .unindent();
 8093
 8094    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8095    cx.set_state(&text);
 8096    cx.set_head_text(&base_text);
 8097    cx.update_editor(|editor, window, cx| {
 8098        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8099    });
 8100
 8101    cx.assert_state_with_diff(
 8102        "
 8103        ˇimpl A {
 8104      -     // this is an uncommitted comment
 8105
 8106            fn b() {
 8107                c();
 8108            }
 8109
 8110      -     // this is another uncommitted comment
 8111      -
 8112            fn d() {
 8113                // e
 8114                // f
 8115            }
 8116        }
 8117
 8118        fn g() {
 8119            // h
 8120        }
 8121    "
 8122        .unindent(),
 8123    );
 8124
 8125    let expected_display_text = "
 8126        impl A {
 8127            // this is an uncommitted comment
 8128
 8129            fn b() {
 8130 8131            }
 8132
 8133            // this is another uncommitted comment
 8134
 8135            fn d() {
 8136 8137            }
 8138        }
 8139
 8140        fn g() {
 8141 8142        }
 8143        "
 8144    .unindent();
 8145
 8146    cx.update_editor(|editor, window, cx| {
 8147        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8148        assert_eq!(editor.display_text(cx), expected_display_text);
 8149    });
 8150}
 8151
 8152#[gpui::test]
 8153async fn test_autoindent(cx: &mut TestAppContext) {
 8154    init_test(cx, |_| {});
 8155
 8156    let language = Arc::new(
 8157        Language::new(
 8158            LanguageConfig {
 8159                brackets: BracketPairConfig {
 8160                    pairs: vec![
 8161                        BracketPair {
 8162                            start: "{".to_string(),
 8163                            end: "}".to_string(),
 8164                            close: false,
 8165                            surround: false,
 8166                            newline: true,
 8167                        },
 8168                        BracketPair {
 8169                            start: "(".to_string(),
 8170                            end: ")".to_string(),
 8171                            close: false,
 8172                            surround: false,
 8173                            newline: true,
 8174                        },
 8175                    ],
 8176                    ..Default::default()
 8177                },
 8178                ..Default::default()
 8179            },
 8180            Some(tree_sitter_rust::LANGUAGE.into()),
 8181        )
 8182        .with_indents_query(
 8183            r#"
 8184                (_ "(" ")" @end) @indent
 8185                (_ "{" "}" @end) @indent
 8186            "#,
 8187        )
 8188        .unwrap(),
 8189    );
 8190
 8191    let text = "fn a() {}";
 8192
 8193    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8194    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8195    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8196    editor
 8197        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8198        .await;
 8199
 8200    editor.update_in(cx, |editor, window, cx| {
 8201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8202            s.select_ranges([5..5, 8..8, 9..9])
 8203        });
 8204        editor.newline(&Newline, window, cx);
 8205        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8206        assert_eq!(
 8207            editor.selections.ranges(cx),
 8208            &[
 8209                Point::new(1, 4)..Point::new(1, 4),
 8210                Point::new(3, 4)..Point::new(3, 4),
 8211                Point::new(5, 0)..Point::new(5, 0)
 8212            ]
 8213        );
 8214    });
 8215}
 8216
 8217#[gpui::test]
 8218async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8219    init_test(cx, |_| {});
 8220
 8221    {
 8222        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8223        cx.set_state(indoc! {"
 8224            impl A {
 8225
 8226                fn b() {}
 8227
 8228            «fn c() {
 8229
 8230            }ˇ»
 8231            }
 8232        "});
 8233
 8234        cx.update_editor(|editor, window, cx| {
 8235            editor.autoindent(&Default::default(), window, cx);
 8236        });
 8237
 8238        cx.assert_editor_state(indoc! {"
 8239            impl A {
 8240
 8241                fn b() {}
 8242
 8243                «fn c() {
 8244
 8245                }ˇ»
 8246            }
 8247        "});
 8248    }
 8249
 8250    {
 8251        let mut cx = EditorTestContext::new_multibuffer(
 8252            cx,
 8253            [indoc! { "
 8254                impl A {
 8255                «
 8256                // a
 8257                fn b(){}
 8258                »
 8259                «
 8260                    }
 8261                    fn c(){}
 8262                »
 8263            "}],
 8264        );
 8265
 8266        let buffer = cx.update_editor(|editor, _, cx| {
 8267            let buffer = editor.buffer().update(cx, |buffer, _| {
 8268                buffer.all_buffers().iter().next().unwrap().clone()
 8269            });
 8270            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8271            buffer
 8272        });
 8273
 8274        cx.run_until_parked();
 8275        cx.update_editor(|editor, window, cx| {
 8276            editor.select_all(&Default::default(), window, cx);
 8277            editor.autoindent(&Default::default(), window, cx)
 8278        });
 8279        cx.run_until_parked();
 8280
 8281        cx.update(|_, cx| {
 8282            assert_eq!(
 8283                buffer.read(cx).text(),
 8284                indoc! { "
 8285                    impl A {
 8286
 8287                        // a
 8288                        fn b(){}
 8289
 8290
 8291                    }
 8292                    fn c(){}
 8293
 8294                " }
 8295            )
 8296        });
 8297    }
 8298}
 8299
 8300#[gpui::test]
 8301async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8302    init_test(cx, |_| {});
 8303
 8304    let mut cx = EditorTestContext::new(cx).await;
 8305
 8306    let language = Arc::new(Language::new(
 8307        LanguageConfig {
 8308            brackets: BracketPairConfig {
 8309                pairs: vec![
 8310                    BracketPair {
 8311                        start: "{".to_string(),
 8312                        end: "}".to_string(),
 8313                        close: true,
 8314                        surround: true,
 8315                        newline: true,
 8316                    },
 8317                    BracketPair {
 8318                        start: "(".to_string(),
 8319                        end: ")".to_string(),
 8320                        close: true,
 8321                        surround: true,
 8322                        newline: true,
 8323                    },
 8324                    BracketPair {
 8325                        start: "/*".to_string(),
 8326                        end: " */".to_string(),
 8327                        close: true,
 8328                        surround: true,
 8329                        newline: true,
 8330                    },
 8331                    BracketPair {
 8332                        start: "[".to_string(),
 8333                        end: "]".to_string(),
 8334                        close: false,
 8335                        surround: false,
 8336                        newline: true,
 8337                    },
 8338                    BracketPair {
 8339                        start: "\"".to_string(),
 8340                        end: "\"".to_string(),
 8341                        close: true,
 8342                        surround: true,
 8343                        newline: false,
 8344                    },
 8345                    BracketPair {
 8346                        start: "<".to_string(),
 8347                        end: ">".to_string(),
 8348                        close: false,
 8349                        surround: true,
 8350                        newline: true,
 8351                    },
 8352                ],
 8353                ..Default::default()
 8354            },
 8355            autoclose_before: "})]".to_string(),
 8356            ..Default::default()
 8357        },
 8358        Some(tree_sitter_rust::LANGUAGE.into()),
 8359    ));
 8360
 8361    cx.language_registry().add(language.clone());
 8362    cx.update_buffer(|buffer, cx| {
 8363        buffer.set_language(Some(language), cx);
 8364    });
 8365
 8366    cx.set_state(
 8367        &r#"
 8368            🏀ˇ
 8369            εˇ
 8370            ❤️ˇ
 8371        "#
 8372        .unindent(),
 8373    );
 8374
 8375    // autoclose multiple nested brackets at multiple cursors
 8376    cx.update_editor(|editor, window, cx| {
 8377        editor.handle_input("{", window, cx);
 8378        editor.handle_input("{", window, cx);
 8379        editor.handle_input("{", window, cx);
 8380    });
 8381    cx.assert_editor_state(
 8382        &"
 8383            🏀{{{ˇ}}}
 8384            ε{{{ˇ}}}
 8385            ❤️{{{ˇ}}}
 8386        "
 8387        .unindent(),
 8388    );
 8389
 8390    // insert a different closing bracket
 8391    cx.update_editor(|editor, window, cx| {
 8392        editor.handle_input(")", window, cx);
 8393    });
 8394    cx.assert_editor_state(
 8395        &"
 8396            🏀{{{)ˇ}}}
 8397            ε{{{)ˇ}}}
 8398            ❤️{{{)ˇ}}}
 8399        "
 8400        .unindent(),
 8401    );
 8402
 8403    // skip over the auto-closed brackets when typing a closing bracket
 8404    cx.update_editor(|editor, window, cx| {
 8405        editor.move_right(&MoveRight, window, cx);
 8406        editor.handle_input("}", window, cx);
 8407        editor.handle_input("}", window, cx);
 8408        editor.handle_input("}", window, cx);
 8409    });
 8410    cx.assert_editor_state(
 8411        &"
 8412            🏀{{{)}}}}ˇ
 8413            ε{{{)}}}}ˇ
 8414            ❤️{{{)}}}}ˇ
 8415        "
 8416        .unindent(),
 8417    );
 8418
 8419    // autoclose multi-character pairs
 8420    cx.set_state(
 8421        &"
 8422            ˇ
 8423            ˇ
 8424        "
 8425        .unindent(),
 8426    );
 8427    cx.update_editor(|editor, window, cx| {
 8428        editor.handle_input("/", window, cx);
 8429        editor.handle_input("*", window, cx);
 8430    });
 8431    cx.assert_editor_state(
 8432        &"
 8433            /*ˇ */
 8434            /*ˇ */
 8435        "
 8436        .unindent(),
 8437    );
 8438
 8439    // one cursor autocloses a multi-character pair, one cursor
 8440    // does not autoclose.
 8441    cx.set_state(
 8442        &"
 8443 8444            ˇ
 8445        "
 8446        .unindent(),
 8447    );
 8448    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8449    cx.assert_editor_state(
 8450        &"
 8451            /*ˇ */
 8452 8453        "
 8454        .unindent(),
 8455    );
 8456
 8457    // Don't autoclose if the next character isn't whitespace and isn't
 8458    // listed in the language's "autoclose_before" section.
 8459    cx.set_state("ˇa b");
 8460    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8461    cx.assert_editor_state("{ˇa b");
 8462
 8463    // Don't autoclose if `close` is false for the bracket pair
 8464    cx.set_state("ˇ");
 8465    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8466    cx.assert_editor_state("");
 8467
 8468    // Surround with brackets if text is selected
 8469    cx.set_state("«aˇ» b");
 8470    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8471    cx.assert_editor_state("{«aˇ»} b");
 8472
 8473    // Autoclose when not immediately after a word character
 8474    cx.set_state("a ˇ");
 8475    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8476    cx.assert_editor_state("a \"ˇ\"");
 8477
 8478    // Autoclose pair where the start and end characters are the same
 8479    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8480    cx.assert_editor_state("a \"\"ˇ");
 8481
 8482    // Don't autoclose when immediately after a word character
 8483    cx.set_state("");
 8484    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8485    cx.assert_editor_state("a\"ˇ");
 8486
 8487    // Do autoclose when after a non-word character
 8488    cx.set_state("");
 8489    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8490    cx.assert_editor_state("{\"ˇ\"");
 8491
 8492    // Non identical pairs autoclose regardless of preceding character
 8493    cx.set_state("");
 8494    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8495    cx.assert_editor_state("a{ˇ}");
 8496
 8497    // Don't autoclose pair if autoclose is disabled
 8498    cx.set_state("ˇ");
 8499    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8500    cx.assert_editor_state("");
 8501
 8502    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8503    cx.set_state("«aˇ» b");
 8504    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8505    cx.assert_editor_state("<«aˇ»> b");
 8506}
 8507
 8508#[gpui::test]
 8509async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8510    init_test(cx, |settings| {
 8511        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8512    });
 8513
 8514    let mut cx = EditorTestContext::new(cx).await;
 8515
 8516    let language = Arc::new(Language::new(
 8517        LanguageConfig {
 8518            brackets: BracketPairConfig {
 8519                pairs: vec![
 8520                    BracketPair {
 8521                        start: "{".to_string(),
 8522                        end: "}".to_string(),
 8523                        close: true,
 8524                        surround: true,
 8525                        newline: true,
 8526                    },
 8527                    BracketPair {
 8528                        start: "(".to_string(),
 8529                        end: ")".to_string(),
 8530                        close: true,
 8531                        surround: true,
 8532                        newline: true,
 8533                    },
 8534                    BracketPair {
 8535                        start: "[".to_string(),
 8536                        end: "]".to_string(),
 8537                        close: false,
 8538                        surround: false,
 8539                        newline: true,
 8540                    },
 8541                ],
 8542                ..Default::default()
 8543            },
 8544            autoclose_before: "})]".to_string(),
 8545            ..Default::default()
 8546        },
 8547        Some(tree_sitter_rust::LANGUAGE.into()),
 8548    ));
 8549
 8550    cx.language_registry().add(language.clone());
 8551    cx.update_buffer(|buffer, cx| {
 8552        buffer.set_language(Some(language), cx);
 8553    });
 8554
 8555    cx.set_state(
 8556        &"
 8557            ˇ
 8558            ˇ
 8559            ˇ
 8560        "
 8561        .unindent(),
 8562    );
 8563
 8564    // ensure only matching closing brackets are skipped over
 8565    cx.update_editor(|editor, window, cx| {
 8566        editor.handle_input("}", window, cx);
 8567        editor.move_left(&MoveLeft, window, cx);
 8568        editor.handle_input(")", window, cx);
 8569        editor.move_left(&MoveLeft, window, cx);
 8570    });
 8571    cx.assert_editor_state(
 8572        &"
 8573            ˇ)}
 8574            ˇ)}
 8575            ˇ)}
 8576        "
 8577        .unindent(),
 8578    );
 8579
 8580    // skip-over closing brackets at multiple cursors
 8581    cx.update_editor(|editor, window, cx| {
 8582        editor.handle_input(")", window, cx);
 8583        editor.handle_input("}", window, cx);
 8584    });
 8585    cx.assert_editor_state(
 8586        &"
 8587            )}ˇ
 8588            )}ˇ
 8589            )}ˇ
 8590        "
 8591        .unindent(),
 8592    );
 8593
 8594    // ignore non-close brackets
 8595    cx.update_editor(|editor, window, cx| {
 8596        editor.handle_input("]", window, cx);
 8597        editor.move_left(&MoveLeft, window, cx);
 8598        editor.handle_input("]", window, cx);
 8599    });
 8600    cx.assert_editor_state(
 8601        &"
 8602            )}]ˇ]
 8603            )}]ˇ]
 8604            )}]ˇ]
 8605        "
 8606        .unindent(),
 8607    );
 8608}
 8609
 8610#[gpui::test]
 8611async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8612    init_test(cx, |_| {});
 8613
 8614    let mut cx = EditorTestContext::new(cx).await;
 8615
 8616    let html_language = Arc::new(
 8617        Language::new(
 8618            LanguageConfig {
 8619                name: "HTML".into(),
 8620                brackets: BracketPairConfig {
 8621                    pairs: vec![
 8622                        BracketPair {
 8623                            start: "<".into(),
 8624                            end: ">".into(),
 8625                            close: true,
 8626                            ..Default::default()
 8627                        },
 8628                        BracketPair {
 8629                            start: "{".into(),
 8630                            end: "}".into(),
 8631                            close: true,
 8632                            ..Default::default()
 8633                        },
 8634                        BracketPair {
 8635                            start: "(".into(),
 8636                            end: ")".into(),
 8637                            close: true,
 8638                            ..Default::default()
 8639                        },
 8640                    ],
 8641                    ..Default::default()
 8642                },
 8643                autoclose_before: "})]>".into(),
 8644                ..Default::default()
 8645            },
 8646            Some(tree_sitter_html::LANGUAGE.into()),
 8647        )
 8648        .with_injection_query(
 8649            r#"
 8650            (script_element
 8651                (raw_text) @injection.content
 8652                (#set! injection.language "javascript"))
 8653            "#,
 8654        )
 8655        .unwrap(),
 8656    );
 8657
 8658    let javascript_language = Arc::new(Language::new(
 8659        LanguageConfig {
 8660            name: "JavaScript".into(),
 8661            brackets: BracketPairConfig {
 8662                pairs: vec![
 8663                    BracketPair {
 8664                        start: "/*".into(),
 8665                        end: " */".into(),
 8666                        close: true,
 8667                        ..Default::default()
 8668                    },
 8669                    BracketPair {
 8670                        start: "{".into(),
 8671                        end: "}".into(),
 8672                        close: true,
 8673                        ..Default::default()
 8674                    },
 8675                    BracketPair {
 8676                        start: "(".into(),
 8677                        end: ")".into(),
 8678                        close: true,
 8679                        ..Default::default()
 8680                    },
 8681                ],
 8682                ..Default::default()
 8683            },
 8684            autoclose_before: "})]>".into(),
 8685            ..Default::default()
 8686        },
 8687        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8688    ));
 8689
 8690    cx.language_registry().add(html_language.clone());
 8691    cx.language_registry().add(javascript_language.clone());
 8692    cx.executor().run_until_parked();
 8693
 8694    cx.update_buffer(|buffer, cx| {
 8695        buffer.set_language(Some(html_language), cx);
 8696    });
 8697
 8698    cx.set_state(
 8699        &r#"
 8700            <body>ˇ
 8701                <script>
 8702                    var x = 1;ˇ
 8703                </script>
 8704            </body>ˇ
 8705        "#
 8706        .unindent(),
 8707    );
 8708
 8709    // Precondition: different languages are active at different locations.
 8710    cx.update_editor(|editor, window, cx| {
 8711        let snapshot = editor.snapshot(window, cx);
 8712        let cursors = editor.selections.ranges::<usize>(cx);
 8713        let languages = cursors
 8714            .iter()
 8715            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8716            .collect::<Vec<_>>();
 8717        assert_eq!(
 8718            languages,
 8719            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8720        );
 8721    });
 8722
 8723    // Angle brackets autoclose in HTML, but not JavaScript.
 8724    cx.update_editor(|editor, window, cx| {
 8725        editor.handle_input("<", window, cx);
 8726        editor.handle_input("a", window, cx);
 8727    });
 8728    cx.assert_editor_state(
 8729        &r#"
 8730            <body><aˇ>
 8731                <script>
 8732                    var x = 1;<aˇ
 8733                </script>
 8734            </body><aˇ>
 8735        "#
 8736        .unindent(),
 8737    );
 8738
 8739    // Curly braces and parens autoclose in both HTML and JavaScript.
 8740    cx.update_editor(|editor, window, cx| {
 8741        editor.handle_input(" b=", window, cx);
 8742        editor.handle_input("{", window, cx);
 8743        editor.handle_input("c", window, cx);
 8744        editor.handle_input("(", window, cx);
 8745    });
 8746    cx.assert_editor_state(
 8747        &r#"
 8748            <body><a b={c(ˇ)}>
 8749                <script>
 8750                    var x = 1;<a b={c(ˇ)}
 8751                </script>
 8752            </body><a b={c(ˇ)}>
 8753        "#
 8754        .unindent(),
 8755    );
 8756
 8757    // Brackets that were already autoclosed are skipped.
 8758    cx.update_editor(|editor, window, cx| {
 8759        editor.handle_input(")", window, cx);
 8760        editor.handle_input("d", window, cx);
 8761        editor.handle_input("}", window, cx);
 8762    });
 8763    cx.assert_editor_state(
 8764        &r#"
 8765            <body><a b={c()d}ˇ>
 8766                <script>
 8767                    var x = 1;<a b={c()d}ˇ
 8768                </script>
 8769            </body><a b={c()d}ˇ>
 8770        "#
 8771        .unindent(),
 8772    );
 8773    cx.update_editor(|editor, window, cx| {
 8774        editor.handle_input(">", window, cx);
 8775    });
 8776    cx.assert_editor_state(
 8777        &r#"
 8778            <body><a b={c()d}>ˇ
 8779                <script>
 8780                    var x = 1;<a b={c()d}>ˇ
 8781                </script>
 8782            </body><a b={c()d}>ˇ
 8783        "#
 8784        .unindent(),
 8785    );
 8786
 8787    // Reset
 8788    cx.set_state(
 8789        &r#"
 8790            <body>ˇ
 8791                <script>
 8792                    var x = 1;ˇ
 8793                </script>
 8794            </body>ˇ
 8795        "#
 8796        .unindent(),
 8797    );
 8798
 8799    cx.update_editor(|editor, window, cx| {
 8800        editor.handle_input("<", window, cx);
 8801    });
 8802    cx.assert_editor_state(
 8803        &r#"
 8804            <body><ˇ>
 8805                <script>
 8806                    var x = 1;<ˇ
 8807                </script>
 8808            </body><ˇ>
 8809        "#
 8810        .unindent(),
 8811    );
 8812
 8813    // When backspacing, the closing angle brackets are removed.
 8814    cx.update_editor(|editor, window, cx| {
 8815        editor.backspace(&Backspace, window, cx);
 8816    });
 8817    cx.assert_editor_state(
 8818        &r#"
 8819            <body>ˇ
 8820                <script>
 8821                    var x = 1;ˇ
 8822                </script>
 8823            </body>ˇ
 8824        "#
 8825        .unindent(),
 8826    );
 8827
 8828    // Block comments autoclose in JavaScript, but not HTML.
 8829    cx.update_editor(|editor, window, cx| {
 8830        editor.handle_input("/", window, cx);
 8831        editor.handle_input("*", window, cx);
 8832    });
 8833    cx.assert_editor_state(
 8834        &r#"
 8835            <body>/*ˇ
 8836                <script>
 8837                    var x = 1;/*ˇ */
 8838                </script>
 8839            </body>/*ˇ
 8840        "#
 8841        .unindent(),
 8842    );
 8843}
 8844
 8845#[gpui::test]
 8846async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8847    init_test(cx, |_| {});
 8848
 8849    let mut cx = EditorTestContext::new(cx).await;
 8850
 8851    let rust_language = Arc::new(
 8852        Language::new(
 8853            LanguageConfig {
 8854                name: "Rust".into(),
 8855                brackets: serde_json::from_value(json!([
 8856                    { "start": "{", "end": "}", "close": true, "newline": true },
 8857                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8858                ]))
 8859                .unwrap(),
 8860                autoclose_before: "})]>".into(),
 8861                ..Default::default()
 8862            },
 8863            Some(tree_sitter_rust::LANGUAGE.into()),
 8864        )
 8865        .with_override_query("(string_literal) @string")
 8866        .unwrap(),
 8867    );
 8868
 8869    cx.language_registry().add(rust_language.clone());
 8870    cx.update_buffer(|buffer, cx| {
 8871        buffer.set_language(Some(rust_language), cx);
 8872    });
 8873
 8874    cx.set_state(
 8875        &r#"
 8876            let x = ˇ
 8877        "#
 8878        .unindent(),
 8879    );
 8880
 8881    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8882    cx.update_editor(|editor, window, cx| {
 8883        editor.handle_input("\"", window, cx);
 8884    });
 8885    cx.assert_editor_state(
 8886        &r#"
 8887            let x = "ˇ"
 8888        "#
 8889        .unindent(),
 8890    );
 8891
 8892    // Inserting another quotation mark. The cursor moves across the existing
 8893    // automatically-inserted quotation mark.
 8894    cx.update_editor(|editor, window, cx| {
 8895        editor.handle_input("\"", window, cx);
 8896    });
 8897    cx.assert_editor_state(
 8898        &r#"
 8899            let x = ""ˇ
 8900        "#
 8901        .unindent(),
 8902    );
 8903
 8904    // Reset
 8905    cx.set_state(
 8906        &r#"
 8907            let x = ˇ
 8908        "#
 8909        .unindent(),
 8910    );
 8911
 8912    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8913    cx.update_editor(|editor, window, cx| {
 8914        editor.handle_input("\"", window, cx);
 8915        editor.handle_input(" ", window, cx);
 8916        editor.move_left(&Default::default(), window, cx);
 8917        editor.handle_input("\\", window, cx);
 8918        editor.handle_input("\"", window, cx);
 8919    });
 8920    cx.assert_editor_state(
 8921        &r#"
 8922            let x = "\"ˇ "
 8923        "#
 8924        .unindent(),
 8925    );
 8926
 8927    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8928    // mark. Nothing is inserted.
 8929    cx.update_editor(|editor, window, cx| {
 8930        editor.move_right(&Default::default(), window, cx);
 8931        editor.handle_input("\"", window, cx);
 8932    });
 8933    cx.assert_editor_state(
 8934        &r#"
 8935            let x = "\" "ˇ
 8936        "#
 8937        .unindent(),
 8938    );
 8939}
 8940
 8941#[gpui::test]
 8942async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8943    init_test(cx, |_| {});
 8944
 8945    let language = Arc::new(Language::new(
 8946        LanguageConfig {
 8947            brackets: BracketPairConfig {
 8948                pairs: vec![
 8949                    BracketPair {
 8950                        start: "{".to_string(),
 8951                        end: "}".to_string(),
 8952                        close: true,
 8953                        surround: true,
 8954                        newline: true,
 8955                    },
 8956                    BracketPair {
 8957                        start: "/* ".to_string(),
 8958                        end: "*/".to_string(),
 8959                        close: true,
 8960                        surround: true,
 8961                        ..Default::default()
 8962                    },
 8963                ],
 8964                ..Default::default()
 8965            },
 8966            ..Default::default()
 8967        },
 8968        Some(tree_sitter_rust::LANGUAGE.into()),
 8969    ));
 8970
 8971    let text = r#"
 8972        a
 8973        b
 8974        c
 8975    "#
 8976    .unindent();
 8977
 8978    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8979    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8980    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8981    editor
 8982        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8983        .await;
 8984
 8985    editor.update_in(cx, |editor, window, cx| {
 8986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8987            s.select_display_ranges([
 8988                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8989                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8990                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8991            ])
 8992        });
 8993
 8994        editor.handle_input("{", window, cx);
 8995        editor.handle_input("{", window, cx);
 8996        editor.handle_input("{", window, cx);
 8997        assert_eq!(
 8998            editor.text(cx),
 8999            "
 9000                {{{a}}}
 9001                {{{b}}}
 9002                {{{c}}}
 9003            "
 9004            .unindent()
 9005        );
 9006        assert_eq!(
 9007            editor.selections.display_ranges(cx),
 9008            [
 9009                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9010                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9011                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9012            ]
 9013        );
 9014
 9015        editor.undo(&Undo, window, cx);
 9016        editor.undo(&Undo, window, cx);
 9017        editor.undo(&Undo, window, cx);
 9018        assert_eq!(
 9019            editor.text(cx),
 9020            "
 9021                a
 9022                b
 9023                c
 9024            "
 9025            .unindent()
 9026        );
 9027        assert_eq!(
 9028            editor.selections.display_ranges(cx),
 9029            [
 9030                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9031                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9032                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9033            ]
 9034        );
 9035
 9036        // Ensure inserting the first character of a multi-byte bracket pair
 9037        // doesn't surround the selections with the bracket.
 9038        editor.handle_input("/", window, cx);
 9039        assert_eq!(
 9040            editor.text(cx),
 9041            "
 9042                /
 9043                /
 9044                /
 9045            "
 9046            .unindent()
 9047        );
 9048        assert_eq!(
 9049            editor.selections.display_ranges(cx),
 9050            [
 9051                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9052                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9053                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9054            ]
 9055        );
 9056
 9057        editor.undo(&Undo, window, cx);
 9058        assert_eq!(
 9059            editor.text(cx),
 9060            "
 9061                a
 9062                b
 9063                c
 9064            "
 9065            .unindent()
 9066        );
 9067        assert_eq!(
 9068            editor.selections.display_ranges(cx),
 9069            [
 9070                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9071                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9072                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9073            ]
 9074        );
 9075
 9076        // Ensure inserting the last character of a multi-byte bracket pair
 9077        // doesn't surround the selections with the bracket.
 9078        editor.handle_input("*", window, cx);
 9079        assert_eq!(
 9080            editor.text(cx),
 9081            "
 9082                *
 9083                *
 9084                *
 9085            "
 9086            .unindent()
 9087        );
 9088        assert_eq!(
 9089            editor.selections.display_ranges(cx),
 9090            [
 9091                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9092                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9093                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9094            ]
 9095        );
 9096    });
 9097}
 9098
 9099#[gpui::test]
 9100async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9101    init_test(cx, |_| {});
 9102
 9103    let language = Arc::new(Language::new(
 9104        LanguageConfig {
 9105            brackets: BracketPairConfig {
 9106                pairs: vec![BracketPair {
 9107                    start: "{".to_string(),
 9108                    end: "}".to_string(),
 9109                    close: true,
 9110                    surround: true,
 9111                    newline: true,
 9112                }],
 9113                ..Default::default()
 9114            },
 9115            autoclose_before: "}".to_string(),
 9116            ..Default::default()
 9117        },
 9118        Some(tree_sitter_rust::LANGUAGE.into()),
 9119    ));
 9120
 9121    let text = r#"
 9122        a
 9123        b
 9124        c
 9125    "#
 9126    .unindent();
 9127
 9128    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9129    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9130    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9131    editor
 9132        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9133        .await;
 9134
 9135    editor.update_in(cx, |editor, window, cx| {
 9136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9137            s.select_ranges([
 9138                Point::new(0, 1)..Point::new(0, 1),
 9139                Point::new(1, 1)..Point::new(1, 1),
 9140                Point::new(2, 1)..Point::new(2, 1),
 9141            ])
 9142        });
 9143
 9144        editor.handle_input("{", window, cx);
 9145        editor.handle_input("{", window, cx);
 9146        editor.handle_input("_", window, cx);
 9147        assert_eq!(
 9148            editor.text(cx),
 9149            "
 9150                a{{_}}
 9151                b{{_}}
 9152                c{{_}}
 9153            "
 9154            .unindent()
 9155        );
 9156        assert_eq!(
 9157            editor.selections.ranges::<Point>(cx),
 9158            [
 9159                Point::new(0, 4)..Point::new(0, 4),
 9160                Point::new(1, 4)..Point::new(1, 4),
 9161                Point::new(2, 4)..Point::new(2, 4)
 9162            ]
 9163        );
 9164
 9165        editor.backspace(&Default::default(), window, cx);
 9166        editor.backspace(&Default::default(), window, cx);
 9167        assert_eq!(
 9168            editor.text(cx),
 9169            "
 9170                a{}
 9171                b{}
 9172                c{}
 9173            "
 9174            .unindent()
 9175        );
 9176        assert_eq!(
 9177            editor.selections.ranges::<Point>(cx),
 9178            [
 9179                Point::new(0, 2)..Point::new(0, 2),
 9180                Point::new(1, 2)..Point::new(1, 2),
 9181                Point::new(2, 2)..Point::new(2, 2)
 9182            ]
 9183        );
 9184
 9185        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9186        assert_eq!(
 9187            editor.text(cx),
 9188            "
 9189                a
 9190                b
 9191                c
 9192            "
 9193            .unindent()
 9194        );
 9195        assert_eq!(
 9196            editor.selections.ranges::<Point>(cx),
 9197            [
 9198                Point::new(0, 1)..Point::new(0, 1),
 9199                Point::new(1, 1)..Point::new(1, 1),
 9200                Point::new(2, 1)..Point::new(2, 1)
 9201            ]
 9202        );
 9203    });
 9204}
 9205
 9206#[gpui::test]
 9207async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9208    init_test(cx, |settings| {
 9209        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9210    });
 9211
 9212    let mut cx = EditorTestContext::new(cx).await;
 9213
 9214    let language = Arc::new(Language::new(
 9215        LanguageConfig {
 9216            brackets: BracketPairConfig {
 9217                pairs: vec![
 9218                    BracketPair {
 9219                        start: "{".to_string(),
 9220                        end: "}".to_string(),
 9221                        close: true,
 9222                        surround: true,
 9223                        newline: true,
 9224                    },
 9225                    BracketPair {
 9226                        start: "(".to_string(),
 9227                        end: ")".to_string(),
 9228                        close: true,
 9229                        surround: true,
 9230                        newline: true,
 9231                    },
 9232                    BracketPair {
 9233                        start: "[".to_string(),
 9234                        end: "]".to_string(),
 9235                        close: false,
 9236                        surround: true,
 9237                        newline: true,
 9238                    },
 9239                ],
 9240                ..Default::default()
 9241            },
 9242            autoclose_before: "})]".to_string(),
 9243            ..Default::default()
 9244        },
 9245        Some(tree_sitter_rust::LANGUAGE.into()),
 9246    ));
 9247
 9248    cx.language_registry().add(language.clone());
 9249    cx.update_buffer(|buffer, cx| {
 9250        buffer.set_language(Some(language), cx);
 9251    });
 9252
 9253    cx.set_state(
 9254        &"
 9255            {(ˇ)}
 9256            [[ˇ]]
 9257            {(ˇ)}
 9258        "
 9259        .unindent(),
 9260    );
 9261
 9262    cx.update_editor(|editor, window, cx| {
 9263        editor.backspace(&Default::default(), window, cx);
 9264        editor.backspace(&Default::default(), window, cx);
 9265    });
 9266
 9267    cx.assert_editor_state(
 9268        &"
 9269            ˇ
 9270            ˇ]]
 9271            ˇ
 9272        "
 9273        .unindent(),
 9274    );
 9275
 9276    cx.update_editor(|editor, window, cx| {
 9277        editor.handle_input("{", window, cx);
 9278        editor.handle_input("{", window, cx);
 9279        editor.move_right(&MoveRight, window, cx);
 9280        editor.move_right(&MoveRight, window, cx);
 9281        editor.move_left(&MoveLeft, window, cx);
 9282        editor.move_left(&MoveLeft, window, cx);
 9283        editor.backspace(&Default::default(), window, cx);
 9284    });
 9285
 9286    cx.assert_editor_state(
 9287        &"
 9288            {ˇ}
 9289            {ˇ}]]
 9290            {ˇ}
 9291        "
 9292        .unindent(),
 9293    );
 9294
 9295    cx.update_editor(|editor, window, cx| {
 9296        editor.backspace(&Default::default(), window, cx);
 9297    });
 9298
 9299    cx.assert_editor_state(
 9300        &"
 9301            ˇ
 9302            ˇ]]
 9303            ˇ
 9304        "
 9305        .unindent(),
 9306    );
 9307}
 9308
 9309#[gpui::test]
 9310async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9311    init_test(cx, |_| {});
 9312
 9313    let language = Arc::new(Language::new(
 9314        LanguageConfig::default(),
 9315        Some(tree_sitter_rust::LANGUAGE.into()),
 9316    ));
 9317
 9318    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9319    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9320    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9321    editor
 9322        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9323        .await;
 9324
 9325    editor.update_in(cx, |editor, window, cx| {
 9326        editor.set_auto_replace_emoji_shortcode(true);
 9327
 9328        editor.handle_input("Hello ", window, cx);
 9329        editor.handle_input(":wave", window, cx);
 9330        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9331
 9332        editor.handle_input(":", window, cx);
 9333        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9334
 9335        editor.handle_input(" :smile", window, cx);
 9336        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9337
 9338        editor.handle_input(":", window, cx);
 9339        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9340
 9341        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9342        editor.handle_input(":wave", window, cx);
 9343        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9344
 9345        editor.handle_input(":", window, cx);
 9346        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9347
 9348        editor.handle_input(":1", window, cx);
 9349        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9350
 9351        editor.handle_input(":", window, cx);
 9352        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9353
 9354        // Ensure shortcode does not get replaced when it is part of a word
 9355        editor.handle_input(" Test:wave", window, cx);
 9356        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9357
 9358        editor.handle_input(":", window, cx);
 9359        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9360
 9361        editor.set_auto_replace_emoji_shortcode(false);
 9362
 9363        // Ensure shortcode does not get replaced when auto replace is off
 9364        editor.handle_input(" :wave", window, cx);
 9365        assert_eq!(
 9366            editor.text(cx),
 9367            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9368        );
 9369
 9370        editor.handle_input(":", window, cx);
 9371        assert_eq!(
 9372            editor.text(cx),
 9373            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9374        );
 9375    });
 9376}
 9377
 9378#[gpui::test]
 9379async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9380    init_test(cx, |_| {});
 9381
 9382    let (text, insertion_ranges) = marked_text_ranges(
 9383        indoc! {"
 9384            ˇ
 9385        "},
 9386        false,
 9387    );
 9388
 9389    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9390    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9391
 9392    _ = editor.update_in(cx, |editor, window, cx| {
 9393        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9394
 9395        editor
 9396            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9397            .unwrap();
 9398
 9399        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9400            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9401            assert_eq!(editor.text(cx), expected_text);
 9402            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9403        }
 9404
 9405        assert(
 9406            editor,
 9407            cx,
 9408            indoc! {"
 9409            type «» =•
 9410            "},
 9411        );
 9412
 9413        assert!(editor.context_menu_visible(), "There should be a matches");
 9414    });
 9415}
 9416
 9417#[gpui::test]
 9418async fn test_snippets(cx: &mut TestAppContext) {
 9419    init_test(cx, |_| {});
 9420
 9421    let mut cx = EditorTestContext::new(cx).await;
 9422
 9423    cx.set_state(indoc! {"
 9424        a.ˇ b
 9425        a.ˇ b
 9426        a.ˇ b
 9427    "});
 9428
 9429    cx.update_editor(|editor, window, cx| {
 9430        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9431        let insertion_ranges = editor
 9432            .selections
 9433            .all(cx)
 9434            .iter()
 9435            .map(|s| s.range().clone())
 9436            .collect::<Vec<_>>();
 9437        editor
 9438            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9439            .unwrap();
 9440    });
 9441
 9442    cx.assert_editor_state(indoc! {"
 9443        a.f(«oneˇ», two, «threeˇ») b
 9444        a.f(«oneˇ», two, «threeˇ») b
 9445        a.f(«oneˇ», two, «threeˇ») b
 9446    "});
 9447
 9448    // Can't move earlier than the first tab stop
 9449    cx.update_editor(|editor, window, cx| {
 9450        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9451    });
 9452    cx.assert_editor_state(indoc! {"
 9453        a.f(«oneˇ», two, «threeˇ») b
 9454        a.f(«oneˇ», two, «threeˇ») b
 9455        a.f(«oneˇ», two, «threeˇ») b
 9456    "});
 9457
 9458    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9459    cx.assert_editor_state(indoc! {"
 9460        a.f(one, «twoˇ», three) b
 9461        a.f(one, «twoˇ», three) b
 9462        a.f(one, «twoˇ», three) b
 9463    "});
 9464
 9465    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9466    cx.assert_editor_state(indoc! {"
 9467        a.f(«oneˇ», two, «threeˇ») b
 9468        a.f(«oneˇ», two, «threeˇ») b
 9469        a.f(«oneˇ», two, «threeˇ») b
 9470    "});
 9471
 9472    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9473    cx.assert_editor_state(indoc! {"
 9474        a.f(one, «twoˇ», three) b
 9475        a.f(one, «twoˇ», three) b
 9476        a.f(one, «twoˇ», three) b
 9477    "});
 9478    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9479    cx.assert_editor_state(indoc! {"
 9480        a.f(one, two, three)ˇ b
 9481        a.f(one, two, three)ˇ b
 9482        a.f(one, two, three)ˇ b
 9483    "});
 9484
 9485    // As soon as the last tab stop is reached, snippet state is gone
 9486    cx.update_editor(|editor, window, cx| {
 9487        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9488    });
 9489    cx.assert_editor_state(indoc! {"
 9490        a.f(one, two, three)ˇ b
 9491        a.f(one, two, three)ˇ b
 9492        a.f(one, two, three)ˇ b
 9493    "});
 9494}
 9495
 9496#[gpui::test]
 9497async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9498    init_test(cx, |_| {});
 9499
 9500    let mut cx = EditorTestContext::new(cx).await;
 9501
 9502    cx.update_editor(|editor, window, cx| {
 9503        let snippet = Snippet::parse(indoc! {"
 9504            /*
 9505             * Multiline comment with leading indentation
 9506             *
 9507             * $1
 9508             */
 9509            $0"})
 9510        .unwrap();
 9511        let insertion_ranges = editor
 9512            .selections
 9513            .all(cx)
 9514            .iter()
 9515            .map(|s| s.range().clone())
 9516            .collect::<Vec<_>>();
 9517        editor
 9518            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9519            .unwrap();
 9520    });
 9521
 9522    cx.assert_editor_state(indoc! {"
 9523        /*
 9524         * Multiline comment with leading indentation
 9525         *
 9526         * ˇ
 9527         */
 9528    "});
 9529
 9530    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9531    cx.assert_editor_state(indoc! {"
 9532        /*
 9533         * Multiline comment with leading indentation
 9534         *
 9535         *•
 9536         */
 9537        ˇ"});
 9538}
 9539
 9540#[gpui::test]
 9541async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9542    init_test(cx, |_| {});
 9543
 9544    let fs = FakeFs::new(cx.executor());
 9545    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9546
 9547    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9548
 9549    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9550    language_registry.add(rust_lang());
 9551    let mut fake_servers = language_registry.register_fake_lsp(
 9552        "Rust",
 9553        FakeLspAdapter {
 9554            capabilities: lsp::ServerCapabilities {
 9555                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9556                ..Default::default()
 9557            },
 9558            ..Default::default()
 9559        },
 9560    );
 9561
 9562    let buffer = project
 9563        .update(cx, |project, cx| {
 9564            project.open_local_buffer(path!("/file.rs"), cx)
 9565        })
 9566        .await
 9567        .unwrap();
 9568
 9569    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9570    let (editor, cx) = cx.add_window_view(|window, cx| {
 9571        build_editor_with_project(project.clone(), buffer, window, cx)
 9572    });
 9573    editor.update_in(cx, |editor, window, cx| {
 9574        editor.set_text("one\ntwo\nthree\n", window, cx)
 9575    });
 9576    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9577
 9578    cx.executor().start_waiting();
 9579    let fake_server = fake_servers.next().await.unwrap();
 9580
 9581    {
 9582        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9583            move |params, _| async move {
 9584                assert_eq!(
 9585                    params.text_document.uri,
 9586                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9587                );
 9588                assert_eq!(params.options.tab_size, 4);
 9589                Ok(Some(vec![lsp::TextEdit::new(
 9590                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9591                    ", ".to_string(),
 9592                )]))
 9593            },
 9594        );
 9595        let save = editor
 9596            .update_in(cx, |editor, window, cx| {
 9597                editor.save(
 9598                    SaveOptions {
 9599                        format: true,
 9600                        autosave: false,
 9601                    },
 9602                    project.clone(),
 9603                    window,
 9604                    cx,
 9605                )
 9606            })
 9607            .unwrap();
 9608        cx.executor().start_waiting();
 9609        save.await;
 9610
 9611        assert_eq!(
 9612            editor.update(cx, |editor, cx| editor.text(cx)),
 9613            "one, two\nthree\n"
 9614        );
 9615        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9616    }
 9617
 9618    {
 9619        editor.update_in(cx, |editor, window, cx| {
 9620            editor.set_text("one\ntwo\nthree\n", window, cx)
 9621        });
 9622        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9623
 9624        // Ensure we can still save even if formatting hangs.
 9625        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9626            move |params, _| async move {
 9627                assert_eq!(
 9628                    params.text_document.uri,
 9629                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9630                );
 9631                futures::future::pending::<()>().await;
 9632                unreachable!()
 9633            },
 9634        );
 9635        let save = editor
 9636            .update_in(cx, |editor, window, cx| {
 9637                editor.save(
 9638                    SaveOptions {
 9639                        format: true,
 9640                        autosave: false,
 9641                    },
 9642                    project.clone(),
 9643                    window,
 9644                    cx,
 9645                )
 9646            })
 9647            .unwrap();
 9648        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9649        cx.executor().start_waiting();
 9650        save.await;
 9651        assert_eq!(
 9652            editor.update(cx, |editor, cx| editor.text(cx)),
 9653            "one\ntwo\nthree\n"
 9654        );
 9655    }
 9656
 9657    // Set rust language override and assert overridden tabsize is sent to language server
 9658    update_test_language_settings(cx, |settings| {
 9659        settings.languages.0.insert(
 9660            "Rust".into(),
 9661            LanguageSettingsContent {
 9662                tab_size: NonZeroU32::new(8),
 9663                ..Default::default()
 9664            },
 9665        );
 9666    });
 9667
 9668    {
 9669        editor.update_in(cx, |editor, window, cx| {
 9670            editor.set_text("somehting_new\n", window, cx)
 9671        });
 9672        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9673        let _formatting_request_signal = fake_server
 9674            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9675                assert_eq!(
 9676                    params.text_document.uri,
 9677                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9678                );
 9679                assert_eq!(params.options.tab_size, 8);
 9680                Ok(Some(vec![]))
 9681            });
 9682        let save = editor
 9683            .update_in(cx, |editor, window, cx| {
 9684                editor.save(
 9685                    SaveOptions {
 9686                        format: true,
 9687                        autosave: false,
 9688                    },
 9689                    project.clone(),
 9690                    window,
 9691                    cx,
 9692                )
 9693            })
 9694            .unwrap();
 9695        cx.executor().start_waiting();
 9696        save.await;
 9697    }
 9698}
 9699
 9700#[gpui::test]
 9701async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9702    init_test(cx, |settings| {
 9703        settings.defaults.ensure_final_newline_on_save = Some(false);
 9704    });
 9705
 9706    let fs = FakeFs::new(cx.executor());
 9707    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9708
 9709    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9710
 9711    let buffer = project
 9712        .update(cx, |project, cx| {
 9713            project.open_local_buffer(path!("/file.txt"), cx)
 9714        })
 9715        .await
 9716        .unwrap();
 9717
 9718    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9719    let (editor, cx) = cx.add_window_view(|window, cx| {
 9720        build_editor_with_project(project.clone(), buffer, window, cx)
 9721    });
 9722    editor.update_in(cx, |editor, window, cx| {
 9723        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9724            s.select_ranges([0..0])
 9725        });
 9726    });
 9727    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9728
 9729    editor.update_in(cx, |editor, window, cx| {
 9730        editor.handle_input("\n", window, cx)
 9731    });
 9732    cx.run_until_parked();
 9733    save(&editor, &project, cx).await;
 9734    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9735
 9736    editor.update_in(cx, |editor, window, cx| {
 9737        editor.undo(&Default::default(), window, cx);
 9738    });
 9739    save(&editor, &project, cx).await;
 9740    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9741
 9742    editor.update_in(cx, |editor, window, cx| {
 9743        editor.redo(&Default::default(), window, cx);
 9744    });
 9745    cx.run_until_parked();
 9746    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9747
 9748    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9749        let save = editor
 9750            .update_in(cx, |editor, window, cx| {
 9751                editor.save(
 9752                    SaveOptions {
 9753                        format: true,
 9754                        autosave: false,
 9755                    },
 9756                    project.clone(),
 9757                    window,
 9758                    cx,
 9759                )
 9760            })
 9761            .unwrap();
 9762        cx.executor().start_waiting();
 9763        save.await;
 9764        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9765    }
 9766}
 9767
 9768#[gpui::test]
 9769async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9770    init_test(cx, |_| {});
 9771
 9772    let cols = 4;
 9773    let rows = 10;
 9774    let sample_text_1 = sample_text(rows, cols, 'a');
 9775    assert_eq!(
 9776        sample_text_1,
 9777        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9778    );
 9779    let sample_text_2 = sample_text(rows, cols, 'l');
 9780    assert_eq!(
 9781        sample_text_2,
 9782        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9783    );
 9784    let sample_text_3 = sample_text(rows, cols, 'v');
 9785    assert_eq!(
 9786        sample_text_3,
 9787        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9788    );
 9789
 9790    let fs = FakeFs::new(cx.executor());
 9791    fs.insert_tree(
 9792        path!("/a"),
 9793        json!({
 9794            "main.rs": sample_text_1,
 9795            "other.rs": sample_text_2,
 9796            "lib.rs": sample_text_3,
 9797        }),
 9798    )
 9799    .await;
 9800
 9801    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9803    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9804
 9805    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9806    language_registry.add(rust_lang());
 9807    let mut fake_servers = language_registry.register_fake_lsp(
 9808        "Rust",
 9809        FakeLspAdapter {
 9810            capabilities: lsp::ServerCapabilities {
 9811                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9812                ..Default::default()
 9813            },
 9814            ..Default::default()
 9815        },
 9816    );
 9817
 9818    let worktree = project.update(cx, |project, cx| {
 9819        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9820        assert_eq!(worktrees.len(), 1);
 9821        worktrees.pop().unwrap()
 9822    });
 9823    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9824
 9825    let buffer_1 = project
 9826        .update(cx, |project, cx| {
 9827            project.open_buffer((worktree_id, "main.rs"), cx)
 9828        })
 9829        .await
 9830        .unwrap();
 9831    let buffer_2 = project
 9832        .update(cx, |project, cx| {
 9833            project.open_buffer((worktree_id, "other.rs"), cx)
 9834        })
 9835        .await
 9836        .unwrap();
 9837    let buffer_3 = project
 9838        .update(cx, |project, cx| {
 9839            project.open_buffer((worktree_id, "lib.rs"), cx)
 9840        })
 9841        .await
 9842        .unwrap();
 9843
 9844    let multi_buffer = cx.new(|cx| {
 9845        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9846        multi_buffer.push_excerpts(
 9847            buffer_1.clone(),
 9848            [
 9849                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9850                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9851                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9852            ],
 9853            cx,
 9854        );
 9855        multi_buffer.push_excerpts(
 9856            buffer_2.clone(),
 9857            [
 9858                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9859                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9860                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9861            ],
 9862            cx,
 9863        );
 9864        multi_buffer.push_excerpts(
 9865            buffer_3.clone(),
 9866            [
 9867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9870            ],
 9871            cx,
 9872        );
 9873        multi_buffer
 9874    });
 9875    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9876        Editor::new(
 9877            EditorMode::full(),
 9878            multi_buffer,
 9879            Some(project.clone()),
 9880            window,
 9881            cx,
 9882        )
 9883    });
 9884
 9885    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9886        editor.change_selections(
 9887            SelectionEffects::scroll(Autoscroll::Next),
 9888            window,
 9889            cx,
 9890            |s| s.select_ranges(Some(1..2)),
 9891        );
 9892        editor.insert("|one|two|three|", window, cx);
 9893    });
 9894    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9895    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9896        editor.change_selections(
 9897            SelectionEffects::scroll(Autoscroll::Next),
 9898            window,
 9899            cx,
 9900            |s| s.select_ranges(Some(60..70)),
 9901        );
 9902        editor.insert("|four|five|six|", window, cx);
 9903    });
 9904    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9905
 9906    // First two buffers should be edited, but not the third one.
 9907    assert_eq!(
 9908        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9909        "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}",
 9910    );
 9911    buffer_1.update(cx, |buffer, _| {
 9912        assert!(buffer.is_dirty());
 9913        assert_eq!(
 9914            buffer.text(),
 9915            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9916        )
 9917    });
 9918    buffer_2.update(cx, |buffer, _| {
 9919        assert!(buffer.is_dirty());
 9920        assert_eq!(
 9921            buffer.text(),
 9922            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9923        )
 9924    });
 9925    buffer_3.update(cx, |buffer, _| {
 9926        assert!(!buffer.is_dirty());
 9927        assert_eq!(buffer.text(), sample_text_3,)
 9928    });
 9929    cx.executor().run_until_parked();
 9930
 9931    cx.executor().start_waiting();
 9932    let save = multi_buffer_editor
 9933        .update_in(cx, |editor, window, cx| {
 9934            editor.save(
 9935                SaveOptions {
 9936                    format: true,
 9937                    autosave: false,
 9938                },
 9939                project.clone(),
 9940                window,
 9941                cx,
 9942            )
 9943        })
 9944        .unwrap();
 9945
 9946    let fake_server = fake_servers.next().await.unwrap();
 9947    fake_server
 9948        .server
 9949        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9950            Ok(Some(vec![lsp::TextEdit::new(
 9951                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9952                format!("[{} formatted]", params.text_document.uri),
 9953            )]))
 9954        })
 9955        .detach();
 9956    save.await;
 9957
 9958    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9959    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9960    assert_eq!(
 9961        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9962        uri!(
 9963            "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}"
 9964        ),
 9965    );
 9966    buffer_1.update(cx, |buffer, _| {
 9967        assert!(!buffer.is_dirty());
 9968        assert_eq!(
 9969            buffer.text(),
 9970            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9971        )
 9972    });
 9973    buffer_2.update(cx, |buffer, _| {
 9974        assert!(!buffer.is_dirty());
 9975        assert_eq!(
 9976            buffer.text(),
 9977            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9978        )
 9979    });
 9980    buffer_3.update(cx, |buffer, _| {
 9981        assert!(!buffer.is_dirty());
 9982        assert_eq!(buffer.text(), sample_text_3,)
 9983    });
 9984}
 9985
 9986#[gpui::test]
 9987async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9988    init_test(cx, |_| {});
 9989
 9990    let fs = FakeFs::new(cx.executor());
 9991    fs.insert_tree(
 9992        path!("/dir"),
 9993        json!({
 9994            "file1.rs": "fn main() { println!(\"hello\"); }",
 9995            "file2.rs": "fn test() { println!(\"test\"); }",
 9996            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9997        }),
 9998    )
 9999    .await;
10000
10001    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10002    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10003    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10004
10005    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10006    language_registry.add(rust_lang());
10007
10008    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10009    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10010
10011    // Open three buffers
10012    let buffer_1 = project
10013        .update(cx, |project, cx| {
10014            project.open_buffer((worktree_id, "file1.rs"), cx)
10015        })
10016        .await
10017        .unwrap();
10018    let buffer_2 = project
10019        .update(cx, |project, cx| {
10020            project.open_buffer((worktree_id, "file2.rs"), cx)
10021        })
10022        .await
10023        .unwrap();
10024    let buffer_3 = project
10025        .update(cx, |project, cx| {
10026            project.open_buffer((worktree_id, "file3.rs"), cx)
10027        })
10028        .await
10029        .unwrap();
10030
10031    // Create a multi-buffer with all three buffers
10032    let multi_buffer = cx.new(|cx| {
10033        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10034        multi_buffer.push_excerpts(
10035            buffer_1.clone(),
10036            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10037            cx,
10038        );
10039        multi_buffer.push_excerpts(
10040            buffer_2.clone(),
10041            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10042            cx,
10043        );
10044        multi_buffer.push_excerpts(
10045            buffer_3.clone(),
10046            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10047            cx,
10048        );
10049        multi_buffer
10050    });
10051
10052    let editor = cx.new_window_entity(|window, cx| {
10053        Editor::new(
10054            EditorMode::full(),
10055            multi_buffer,
10056            Some(project.clone()),
10057            window,
10058            cx,
10059        )
10060    });
10061
10062    // Edit only the first buffer
10063    editor.update_in(cx, |editor, window, cx| {
10064        editor.change_selections(
10065            SelectionEffects::scroll(Autoscroll::Next),
10066            window,
10067            cx,
10068            |s| s.select_ranges(Some(10..10)),
10069        );
10070        editor.insert("// edited", window, cx);
10071    });
10072
10073    // Verify that only buffer 1 is dirty
10074    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10075    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10076    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10077
10078    // Get write counts after file creation (files were created with initial content)
10079    // We expect each file to have been written once during creation
10080    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10081    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10082    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10083
10084    // Perform autosave
10085    let save_task = editor.update_in(cx, |editor, window, cx| {
10086        editor.save(
10087            SaveOptions {
10088                format: true,
10089                autosave: true,
10090            },
10091            project.clone(),
10092            window,
10093            cx,
10094        )
10095    });
10096    save_task.await.unwrap();
10097
10098    // Only the dirty buffer should have been saved
10099    assert_eq!(
10100        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10101        1,
10102        "Buffer 1 was dirty, so it should have been written once during autosave"
10103    );
10104    assert_eq!(
10105        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10106        0,
10107        "Buffer 2 was clean, so it should not have been written during autosave"
10108    );
10109    assert_eq!(
10110        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10111        0,
10112        "Buffer 3 was clean, so it should not have been written during autosave"
10113    );
10114
10115    // Verify buffer states after autosave
10116    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10117    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10118    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10119
10120    // Now perform a manual save (format = true)
10121    let save_task = editor.update_in(cx, |editor, window, cx| {
10122        editor.save(
10123            SaveOptions {
10124                format: true,
10125                autosave: false,
10126            },
10127            project.clone(),
10128            window,
10129            cx,
10130        )
10131    });
10132    save_task.await.unwrap();
10133
10134    // During manual save, clean buffers don't get written to disk
10135    // They just get did_save called for language server notifications
10136    assert_eq!(
10137        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10138        1,
10139        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10140    );
10141    assert_eq!(
10142        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10143        0,
10144        "Buffer 2 should not have been written at all"
10145    );
10146    assert_eq!(
10147        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10148        0,
10149        "Buffer 3 should not have been written at all"
10150    );
10151}
10152
10153async fn setup_range_format_test(
10154    cx: &mut TestAppContext,
10155) -> (
10156    Entity<Project>,
10157    Entity<Editor>,
10158    &mut gpui::VisualTestContext,
10159    lsp::FakeLanguageServer,
10160) {
10161    init_test(cx, |_| {});
10162
10163    let fs = FakeFs::new(cx.executor());
10164    fs.insert_file(path!("/file.rs"), Default::default()).await;
10165
10166    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10167
10168    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10169    language_registry.add(rust_lang());
10170    let mut fake_servers = language_registry.register_fake_lsp(
10171        "Rust",
10172        FakeLspAdapter {
10173            capabilities: lsp::ServerCapabilities {
10174                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10175                ..lsp::ServerCapabilities::default()
10176            },
10177            ..FakeLspAdapter::default()
10178        },
10179    );
10180
10181    let buffer = project
10182        .update(cx, |project, cx| {
10183            project.open_local_buffer(path!("/file.rs"), cx)
10184        })
10185        .await
10186        .unwrap();
10187
10188    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10189    let (editor, cx) = cx.add_window_view(|window, cx| {
10190        build_editor_with_project(project.clone(), buffer, window, cx)
10191    });
10192
10193    cx.executor().start_waiting();
10194    let fake_server = fake_servers.next().await.unwrap();
10195
10196    (project, editor, cx, fake_server)
10197}
10198
10199#[gpui::test]
10200async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10201    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10202
10203    editor.update_in(cx, |editor, window, cx| {
10204        editor.set_text("one\ntwo\nthree\n", window, cx)
10205    });
10206    assert!(cx.read(|cx| editor.is_dirty(cx)));
10207
10208    let save = editor
10209        .update_in(cx, |editor, window, cx| {
10210            editor.save(
10211                SaveOptions {
10212                    format: true,
10213                    autosave: false,
10214                },
10215                project.clone(),
10216                window,
10217                cx,
10218            )
10219        })
10220        .unwrap();
10221    fake_server
10222        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10223            assert_eq!(
10224                params.text_document.uri,
10225                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10226            );
10227            assert_eq!(params.options.tab_size, 4);
10228            Ok(Some(vec![lsp::TextEdit::new(
10229                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10230                ", ".to_string(),
10231            )]))
10232        })
10233        .next()
10234        .await;
10235    cx.executor().start_waiting();
10236    save.await;
10237    assert_eq!(
10238        editor.update(cx, |editor, cx| editor.text(cx)),
10239        "one, two\nthree\n"
10240    );
10241    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10242}
10243
10244#[gpui::test]
10245async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10246    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10247
10248    editor.update_in(cx, |editor, window, cx| {
10249        editor.set_text("one\ntwo\nthree\n", window, cx)
10250    });
10251    assert!(cx.read(|cx| editor.is_dirty(cx)));
10252
10253    // Test that save still works when formatting hangs
10254    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10255        move |params, _| async move {
10256            assert_eq!(
10257                params.text_document.uri,
10258                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10259            );
10260            futures::future::pending::<()>().await;
10261            unreachable!()
10262        },
10263    );
10264    let save = editor
10265        .update_in(cx, |editor, window, cx| {
10266            editor.save(
10267                SaveOptions {
10268                    format: true,
10269                    autosave: false,
10270                },
10271                project.clone(),
10272                window,
10273                cx,
10274            )
10275        })
10276        .unwrap();
10277    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10278    cx.executor().start_waiting();
10279    save.await;
10280    assert_eq!(
10281        editor.update(cx, |editor, cx| editor.text(cx)),
10282        "one\ntwo\nthree\n"
10283    );
10284    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10285}
10286
10287#[gpui::test]
10288async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10289    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10290
10291    // Buffer starts clean, no formatting should be requested
10292    let save = editor
10293        .update_in(cx, |editor, window, cx| {
10294            editor.save(
10295                SaveOptions {
10296                    format: false,
10297                    autosave: false,
10298                },
10299                project.clone(),
10300                window,
10301                cx,
10302            )
10303        })
10304        .unwrap();
10305    let _pending_format_request = fake_server
10306        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10307            panic!("Should not be invoked");
10308        })
10309        .next();
10310    cx.executor().start_waiting();
10311    save.await;
10312    cx.run_until_parked();
10313}
10314
10315#[gpui::test]
10316async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10317    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10318
10319    // Set Rust language override and assert overridden tabsize is sent to language server
10320    update_test_language_settings(cx, |settings| {
10321        settings.languages.0.insert(
10322            "Rust".into(),
10323            LanguageSettingsContent {
10324                tab_size: NonZeroU32::new(8),
10325                ..Default::default()
10326            },
10327        );
10328    });
10329
10330    editor.update_in(cx, |editor, window, cx| {
10331        editor.set_text("something_new\n", window, cx)
10332    });
10333    assert!(cx.read(|cx| editor.is_dirty(cx)));
10334    let save = editor
10335        .update_in(cx, |editor, window, cx| {
10336            editor.save(
10337                SaveOptions {
10338                    format: true,
10339                    autosave: false,
10340                },
10341                project.clone(),
10342                window,
10343                cx,
10344            )
10345        })
10346        .unwrap();
10347    fake_server
10348        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10349            assert_eq!(
10350                params.text_document.uri,
10351                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10352            );
10353            assert_eq!(params.options.tab_size, 8);
10354            Ok(Some(Vec::new()))
10355        })
10356        .next()
10357        .await;
10358    save.await;
10359}
10360
10361#[gpui::test]
10362async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10363    init_test(cx, |settings| {
10364        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10365            Formatter::LanguageServer { name: None },
10366        )))
10367    });
10368
10369    let fs = FakeFs::new(cx.executor());
10370    fs.insert_file(path!("/file.rs"), Default::default()).await;
10371
10372    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10373
10374    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10375    language_registry.add(Arc::new(Language::new(
10376        LanguageConfig {
10377            name: "Rust".into(),
10378            matcher: LanguageMatcher {
10379                path_suffixes: vec!["rs".to_string()],
10380                ..Default::default()
10381            },
10382            ..LanguageConfig::default()
10383        },
10384        Some(tree_sitter_rust::LANGUAGE.into()),
10385    )));
10386    update_test_language_settings(cx, |settings| {
10387        // Enable Prettier formatting for the same buffer, and ensure
10388        // LSP is called instead of Prettier.
10389        settings.defaults.prettier = Some(PrettierSettings {
10390            allowed: true,
10391            ..PrettierSettings::default()
10392        });
10393    });
10394    let mut fake_servers = language_registry.register_fake_lsp(
10395        "Rust",
10396        FakeLspAdapter {
10397            capabilities: lsp::ServerCapabilities {
10398                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10399                ..Default::default()
10400            },
10401            ..Default::default()
10402        },
10403    );
10404
10405    let buffer = project
10406        .update(cx, |project, cx| {
10407            project.open_local_buffer(path!("/file.rs"), cx)
10408        })
10409        .await
10410        .unwrap();
10411
10412    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10413    let (editor, cx) = cx.add_window_view(|window, cx| {
10414        build_editor_with_project(project.clone(), buffer, window, cx)
10415    });
10416    editor.update_in(cx, |editor, window, cx| {
10417        editor.set_text("one\ntwo\nthree\n", window, cx)
10418    });
10419
10420    cx.executor().start_waiting();
10421    let fake_server = fake_servers.next().await.unwrap();
10422
10423    let format = editor
10424        .update_in(cx, |editor, window, cx| {
10425            editor.perform_format(
10426                project.clone(),
10427                FormatTrigger::Manual,
10428                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10429                window,
10430                cx,
10431            )
10432        })
10433        .unwrap();
10434    fake_server
10435        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10436            assert_eq!(
10437                params.text_document.uri,
10438                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10439            );
10440            assert_eq!(params.options.tab_size, 4);
10441            Ok(Some(vec![lsp::TextEdit::new(
10442                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10443                ", ".to_string(),
10444            )]))
10445        })
10446        .next()
10447        .await;
10448    cx.executor().start_waiting();
10449    format.await;
10450    assert_eq!(
10451        editor.update(cx, |editor, cx| editor.text(cx)),
10452        "one, two\nthree\n"
10453    );
10454
10455    editor.update_in(cx, |editor, window, cx| {
10456        editor.set_text("one\ntwo\nthree\n", window, cx)
10457    });
10458    // Ensure we don't lock if formatting hangs.
10459    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10460        move |params, _| async move {
10461            assert_eq!(
10462                params.text_document.uri,
10463                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10464            );
10465            futures::future::pending::<()>().await;
10466            unreachable!()
10467        },
10468    );
10469    let format = editor
10470        .update_in(cx, |editor, window, cx| {
10471            editor.perform_format(
10472                project,
10473                FormatTrigger::Manual,
10474                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10475                window,
10476                cx,
10477            )
10478        })
10479        .unwrap();
10480    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10481    cx.executor().start_waiting();
10482    format.await;
10483    assert_eq!(
10484        editor.update(cx, |editor, cx| editor.text(cx)),
10485        "one\ntwo\nthree\n"
10486    );
10487}
10488
10489#[gpui::test]
10490async fn test_multiple_formatters(cx: &mut TestAppContext) {
10491    init_test(cx, |settings| {
10492        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10493        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10494            Formatter::LanguageServer { name: None },
10495            Formatter::CodeActions(
10496                [
10497                    ("code-action-1".into(), true),
10498                    ("code-action-2".into(), true),
10499                ]
10500                .into_iter()
10501                .collect(),
10502            ),
10503        ])))
10504    });
10505
10506    let fs = FakeFs::new(cx.executor());
10507    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10508        .await;
10509
10510    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10511    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10512    language_registry.add(rust_lang());
10513
10514    let mut fake_servers = language_registry.register_fake_lsp(
10515        "Rust",
10516        FakeLspAdapter {
10517            capabilities: lsp::ServerCapabilities {
10518                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10519                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10520                    commands: vec!["the-command-for-code-action-1".into()],
10521                    ..Default::default()
10522                }),
10523                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10524                ..Default::default()
10525            },
10526            ..Default::default()
10527        },
10528    );
10529
10530    let buffer = project
10531        .update(cx, |project, cx| {
10532            project.open_local_buffer(path!("/file.rs"), cx)
10533        })
10534        .await
10535        .unwrap();
10536
10537    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10538    let (editor, cx) = cx.add_window_view(|window, cx| {
10539        build_editor_with_project(project.clone(), buffer, window, cx)
10540    });
10541
10542    cx.executor().start_waiting();
10543
10544    let fake_server = fake_servers.next().await.unwrap();
10545    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10546        move |_params, _| async move {
10547            Ok(Some(vec![lsp::TextEdit::new(
10548                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10549                "applied-formatting\n".to_string(),
10550            )]))
10551        },
10552    );
10553    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10554        move |params, _| async move {
10555            assert_eq!(
10556                params.context.only,
10557                Some(vec!["code-action-1".into(), "code-action-2".into()])
10558            );
10559            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10560            Ok(Some(vec![
10561                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10562                    kind: Some("code-action-1".into()),
10563                    edit: Some(lsp::WorkspaceEdit::new(
10564                        [(
10565                            uri.clone(),
10566                            vec![lsp::TextEdit::new(
10567                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10568                                "applied-code-action-1-edit\n".to_string(),
10569                            )],
10570                        )]
10571                        .into_iter()
10572                        .collect(),
10573                    )),
10574                    command: Some(lsp::Command {
10575                        command: "the-command-for-code-action-1".into(),
10576                        ..Default::default()
10577                    }),
10578                    ..Default::default()
10579                }),
10580                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10581                    kind: Some("code-action-2".into()),
10582                    edit: Some(lsp::WorkspaceEdit::new(
10583                        [(
10584                            uri.clone(),
10585                            vec![lsp::TextEdit::new(
10586                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10587                                "applied-code-action-2-edit\n".to_string(),
10588                            )],
10589                        )]
10590                        .into_iter()
10591                        .collect(),
10592                    )),
10593                    ..Default::default()
10594                }),
10595            ]))
10596        },
10597    );
10598
10599    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10600        move |params, _| async move { Ok(params) }
10601    });
10602
10603    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10604    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10605        let fake = fake_server.clone();
10606        let lock = command_lock.clone();
10607        move |params, _| {
10608            assert_eq!(params.command, "the-command-for-code-action-1");
10609            let fake = fake.clone();
10610            let lock = lock.clone();
10611            async move {
10612                lock.lock().await;
10613                fake.server
10614                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10615                        label: None,
10616                        edit: lsp::WorkspaceEdit {
10617                            changes: Some(
10618                                [(
10619                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10620                                    vec![lsp::TextEdit {
10621                                        range: lsp::Range::new(
10622                                            lsp::Position::new(0, 0),
10623                                            lsp::Position::new(0, 0),
10624                                        ),
10625                                        new_text: "applied-code-action-1-command\n".into(),
10626                                    }],
10627                                )]
10628                                .into_iter()
10629                                .collect(),
10630                            ),
10631                            ..Default::default()
10632                        },
10633                    })
10634                    .await
10635                    .into_response()
10636                    .unwrap();
10637                Ok(Some(json!(null)))
10638            }
10639        }
10640    });
10641
10642    cx.executor().start_waiting();
10643    editor
10644        .update_in(cx, |editor, window, cx| {
10645            editor.perform_format(
10646                project.clone(),
10647                FormatTrigger::Manual,
10648                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10649                window,
10650                cx,
10651            )
10652        })
10653        .unwrap()
10654        .await;
10655    editor.update(cx, |editor, cx| {
10656        assert_eq!(
10657            editor.text(cx),
10658            r#"
10659                applied-code-action-2-edit
10660                applied-code-action-1-command
10661                applied-code-action-1-edit
10662                applied-formatting
10663                one
10664                two
10665                three
10666            "#
10667            .unindent()
10668        );
10669    });
10670
10671    editor.update_in(cx, |editor, window, cx| {
10672        editor.undo(&Default::default(), window, cx);
10673        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10674    });
10675
10676    // Perform a manual edit while waiting for an LSP command
10677    // that's being run as part of a formatting code action.
10678    let lock_guard = command_lock.lock().await;
10679    let format = editor
10680        .update_in(cx, |editor, window, cx| {
10681            editor.perform_format(
10682                project.clone(),
10683                FormatTrigger::Manual,
10684                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10685                window,
10686                cx,
10687            )
10688        })
10689        .unwrap();
10690    cx.run_until_parked();
10691    editor.update(cx, |editor, cx| {
10692        assert_eq!(
10693            editor.text(cx),
10694            r#"
10695                applied-code-action-1-edit
10696                applied-formatting
10697                one
10698                two
10699                three
10700            "#
10701            .unindent()
10702        );
10703
10704        editor.buffer.update(cx, |buffer, cx| {
10705            let ix = buffer.len(cx);
10706            buffer.edit([(ix..ix, "edited\n")], None, cx);
10707        });
10708    });
10709
10710    // Allow the LSP command to proceed. Because the buffer was edited,
10711    // the second code action will not be run.
10712    drop(lock_guard);
10713    format.await;
10714    editor.update_in(cx, |editor, window, cx| {
10715        assert_eq!(
10716            editor.text(cx),
10717            r#"
10718                applied-code-action-1-command
10719                applied-code-action-1-edit
10720                applied-formatting
10721                one
10722                two
10723                three
10724                edited
10725            "#
10726            .unindent()
10727        );
10728
10729        // The manual edit is undone first, because it is the last thing the user did
10730        // (even though the command completed afterwards).
10731        editor.undo(&Default::default(), window, cx);
10732        assert_eq!(
10733            editor.text(cx),
10734            r#"
10735                applied-code-action-1-command
10736                applied-code-action-1-edit
10737                applied-formatting
10738                one
10739                two
10740                three
10741            "#
10742            .unindent()
10743        );
10744
10745        // All the formatting (including the command, which completed after the manual edit)
10746        // is undone together.
10747        editor.undo(&Default::default(), window, cx);
10748        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10749    });
10750}
10751
10752#[gpui::test]
10753async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10754    init_test(cx, |settings| {
10755        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10756            Formatter::LanguageServer { name: None },
10757        ])))
10758    });
10759
10760    let fs = FakeFs::new(cx.executor());
10761    fs.insert_file(path!("/file.ts"), Default::default()).await;
10762
10763    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10764
10765    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10766    language_registry.add(Arc::new(Language::new(
10767        LanguageConfig {
10768            name: "TypeScript".into(),
10769            matcher: LanguageMatcher {
10770                path_suffixes: vec!["ts".to_string()],
10771                ..Default::default()
10772            },
10773            ..LanguageConfig::default()
10774        },
10775        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10776    )));
10777    update_test_language_settings(cx, |settings| {
10778        settings.defaults.prettier = Some(PrettierSettings {
10779            allowed: true,
10780            ..PrettierSettings::default()
10781        });
10782    });
10783    let mut fake_servers = language_registry.register_fake_lsp(
10784        "TypeScript",
10785        FakeLspAdapter {
10786            capabilities: lsp::ServerCapabilities {
10787                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10788                ..Default::default()
10789            },
10790            ..Default::default()
10791        },
10792    );
10793
10794    let buffer = project
10795        .update(cx, |project, cx| {
10796            project.open_local_buffer(path!("/file.ts"), cx)
10797        })
10798        .await
10799        .unwrap();
10800
10801    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10802    let (editor, cx) = cx.add_window_view(|window, cx| {
10803        build_editor_with_project(project.clone(), buffer, window, cx)
10804    });
10805    editor.update_in(cx, |editor, window, cx| {
10806        editor.set_text(
10807            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10808            window,
10809            cx,
10810        )
10811    });
10812
10813    cx.executor().start_waiting();
10814    let fake_server = fake_servers.next().await.unwrap();
10815
10816    let format = editor
10817        .update_in(cx, |editor, window, cx| {
10818            editor.perform_code_action_kind(
10819                project.clone(),
10820                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10821                window,
10822                cx,
10823            )
10824        })
10825        .unwrap();
10826    fake_server
10827        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10828            assert_eq!(
10829                params.text_document.uri,
10830                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10831            );
10832            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10833                lsp::CodeAction {
10834                    title: "Organize Imports".to_string(),
10835                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10836                    edit: Some(lsp::WorkspaceEdit {
10837                        changes: Some(
10838                            [(
10839                                params.text_document.uri.clone(),
10840                                vec![lsp::TextEdit::new(
10841                                    lsp::Range::new(
10842                                        lsp::Position::new(1, 0),
10843                                        lsp::Position::new(2, 0),
10844                                    ),
10845                                    "".to_string(),
10846                                )],
10847                            )]
10848                            .into_iter()
10849                            .collect(),
10850                        ),
10851                        ..Default::default()
10852                    }),
10853                    ..Default::default()
10854                },
10855            )]))
10856        })
10857        .next()
10858        .await;
10859    cx.executor().start_waiting();
10860    format.await;
10861    assert_eq!(
10862        editor.update(cx, |editor, cx| editor.text(cx)),
10863        "import { a } from 'module';\n\nconst x = a;\n"
10864    );
10865
10866    editor.update_in(cx, |editor, window, cx| {
10867        editor.set_text(
10868            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10869            window,
10870            cx,
10871        )
10872    });
10873    // Ensure we don't lock if code action hangs.
10874    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10875        move |params, _| async move {
10876            assert_eq!(
10877                params.text_document.uri,
10878                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10879            );
10880            futures::future::pending::<()>().await;
10881            unreachable!()
10882        },
10883    );
10884    let format = editor
10885        .update_in(cx, |editor, window, cx| {
10886            editor.perform_code_action_kind(
10887                project,
10888                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10889                window,
10890                cx,
10891            )
10892        })
10893        .unwrap();
10894    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10895    cx.executor().start_waiting();
10896    format.await;
10897    assert_eq!(
10898        editor.update(cx, |editor, cx| editor.text(cx)),
10899        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10900    );
10901}
10902
10903#[gpui::test]
10904async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10905    init_test(cx, |_| {});
10906
10907    let mut cx = EditorLspTestContext::new_rust(
10908        lsp::ServerCapabilities {
10909            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10910            ..Default::default()
10911        },
10912        cx,
10913    )
10914    .await;
10915
10916    cx.set_state(indoc! {"
10917        one.twoˇ
10918    "});
10919
10920    // The format request takes a long time. When it completes, it inserts
10921    // a newline and an indent before the `.`
10922    cx.lsp
10923        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10924            let executor = cx.background_executor().clone();
10925            async move {
10926                executor.timer(Duration::from_millis(100)).await;
10927                Ok(Some(vec![lsp::TextEdit {
10928                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10929                    new_text: "\n    ".into(),
10930                }]))
10931            }
10932        });
10933
10934    // Submit a format request.
10935    let format_1 = cx
10936        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10937        .unwrap();
10938    cx.executor().run_until_parked();
10939
10940    // Submit a second format request.
10941    let format_2 = cx
10942        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10943        .unwrap();
10944    cx.executor().run_until_parked();
10945
10946    // Wait for both format requests to complete
10947    cx.executor().advance_clock(Duration::from_millis(200));
10948    cx.executor().start_waiting();
10949    format_1.await.unwrap();
10950    cx.executor().start_waiting();
10951    format_2.await.unwrap();
10952
10953    // The formatting edits only happens once.
10954    cx.assert_editor_state(indoc! {"
10955        one
10956            .twoˇ
10957    "});
10958}
10959
10960#[gpui::test]
10961async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10962    init_test(cx, |settings| {
10963        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10964    });
10965
10966    let mut cx = EditorLspTestContext::new_rust(
10967        lsp::ServerCapabilities {
10968            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10969            ..Default::default()
10970        },
10971        cx,
10972    )
10973    .await;
10974
10975    // Set up a buffer white some trailing whitespace and no trailing newline.
10976    cx.set_state(
10977        &[
10978            "one ",   //
10979            "twoˇ",   //
10980            "three ", //
10981            "four",   //
10982        ]
10983        .join("\n"),
10984    );
10985
10986    // Submit a format request.
10987    let format = cx
10988        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10989        .unwrap();
10990
10991    // Record which buffer changes have been sent to the language server
10992    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10993    cx.lsp
10994        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10995            let buffer_changes = buffer_changes.clone();
10996            move |params, _| {
10997                buffer_changes.lock().extend(
10998                    params
10999                        .content_changes
11000                        .into_iter()
11001                        .map(|e| (e.range.unwrap(), e.text)),
11002                );
11003            }
11004        });
11005
11006    // Handle formatting requests to the language server.
11007    cx.lsp
11008        .set_request_handler::<lsp::request::Formatting, _, _>({
11009            let buffer_changes = buffer_changes.clone();
11010            move |_, _| {
11011                // When formatting is requested, trailing whitespace has already been stripped,
11012                // and the trailing newline has already been added.
11013                assert_eq!(
11014                    &buffer_changes.lock()[1..],
11015                    &[
11016                        (
11017                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11018                            "".into()
11019                        ),
11020                        (
11021                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11022                            "".into()
11023                        ),
11024                        (
11025                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11026                            "\n".into()
11027                        ),
11028                    ]
11029                );
11030
11031                // Insert blank lines between each line of the buffer.
11032                async move {
11033                    Ok(Some(vec![
11034                        lsp::TextEdit {
11035                            range: lsp::Range::new(
11036                                lsp::Position::new(1, 0),
11037                                lsp::Position::new(1, 0),
11038                            ),
11039                            new_text: "\n".into(),
11040                        },
11041                        lsp::TextEdit {
11042                            range: lsp::Range::new(
11043                                lsp::Position::new(2, 0),
11044                                lsp::Position::new(2, 0),
11045                            ),
11046                            new_text: "\n".into(),
11047                        },
11048                    ]))
11049                }
11050            }
11051        });
11052
11053    // After formatting the buffer, the trailing whitespace is stripped,
11054    // a newline is appended, and the edits provided by the language server
11055    // have been applied.
11056    format.await.unwrap();
11057    cx.assert_editor_state(
11058        &[
11059            "one",   //
11060            "",      //
11061            "twoˇ",  //
11062            "",      //
11063            "three", //
11064            "four",  //
11065            "",      //
11066        ]
11067        .join("\n"),
11068    );
11069
11070    // Undoing the formatting undoes the trailing whitespace removal, the
11071    // trailing newline, and the LSP edits.
11072    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11073    cx.assert_editor_state(
11074        &[
11075            "one ",   //
11076            "twoˇ",   //
11077            "three ", //
11078            "four",   //
11079        ]
11080        .join("\n"),
11081    );
11082}
11083
11084#[gpui::test]
11085async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11086    cx: &mut TestAppContext,
11087) {
11088    init_test(cx, |_| {});
11089
11090    cx.update(|cx| {
11091        cx.update_global::<SettingsStore, _>(|settings, cx| {
11092            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11093                settings.auto_signature_help = Some(true);
11094            });
11095        });
11096    });
11097
11098    let mut cx = EditorLspTestContext::new_rust(
11099        lsp::ServerCapabilities {
11100            signature_help_provider: Some(lsp::SignatureHelpOptions {
11101                ..Default::default()
11102            }),
11103            ..Default::default()
11104        },
11105        cx,
11106    )
11107    .await;
11108
11109    let language = Language::new(
11110        LanguageConfig {
11111            name: "Rust".into(),
11112            brackets: BracketPairConfig {
11113                pairs: vec![
11114                    BracketPair {
11115                        start: "{".to_string(),
11116                        end: "}".to_string(),
11117                        close: true,
11118                        surround: true,
11119                        newline: true,
11120                    },
11121                    BracketPair {
11122                        start: "(".to_string(),
11123                        end: ")".to_string(),
11124                        close: true,
11125                        surround: true,
11126                        newline: true,
11127                    },
11128                    BracketPair {
11129                        start: "/*".to_string(),
11130                        end: " */".to_string(),
11131                        close: true,
11132                        surround: true,
11133                        newline: true,
11134                    },
11135                    BracketPair {
11136                        start: "[".to_string(),
11137                        end: "]".to_string(),
11138                        close: false,
11139                        surround: false,
11140                        newline: true,
11141                    },
11142                    BracketPair {
11143                        start: "\"".to_string(),
11144                        end: "\"".to_string(),
11145                        close: true,
11146                        surround: true,
11147                        newline: false,
11148                    },
11149                    BracketPair {
11150                        start: "<".to_string(),
11151                        end: ">".to_string(),
11152                        close: false,
11153                        surround: true,
11154                        newline: true,
11155                    },
11156                ],
11157                ..Default::default()
11158            },
11159            autoclose_before: "})]".to_string(),
11160            ..Default::default()
11161        },
11162        Some(tree_sitter_rust::LANGUAGE.into()),
11163    );
11164    let language = Arc::new(language);
11165
11166    cx.language_registry().add(language.clone());
11167    cx.update_buffer(|buffer, cx| {
11168        buffer.set_language(Some(language), cx);
11169    });
11170
11171    cx.set_state(
11172        &r#"
11173            fn main() {
11174                sampleˇ
11175            }
11176        "#
11177        .unindent(),
11178    );
11179
11180    cx.update_editor(|editor, window, cx| {
11181        editor.handle_input("(", window, cx);
11182    });
11183    cx.assert_editor_state(
11184        &"
11185            fn main() {
11186                sample(ˇ)
11187            }
11188        "
11189        .unindent(),
11190    );
11191
11192    let mocked_response = lsp::SignatureHelp {
11193        signatures: vec![lsp::SignatureInformation {
11194            label: "fn sample(param1: u8, param2: u8)".to_string(),
11195            documentation: None,
11196            parameters: Some(vec![
11197                lsp::ParameterInformation {
11198                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11199                    documentation: None,
11200                },
11201                lsp::ParameterInformation {
11202                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11203                    documentation: None,
11204                },
11205            ]),
11206            active_parameter: None,
11207        }],
11208        active_signature: Some(0),
11209        active_parameter: Some(0),
11210    };
11211    handle_signature_help_request(&mut cx, mocked_response).await;
11212
11213    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11214        .await;
11215
11216    cx.editor(|editor, _, _| {
11217        let signature_help_state = editor.signature_help_state.popover().cloned();
11218        let signature = signature_help_state.unwrap();
11219        assert_eq!(
11220            signature.signatures[signature.current_signature].label,
11221            "fn sample(param1: u8, param2: u8)"
11222        );
11223    });
11224}
11225
11226#[gpui::test]
11227async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11228    init_test(cx, |_| {});
11229
11230    cx.update(|cx| {
11231        cx.update_global::<SettingsStore, _>(|settings, cx| {
11232            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11233                settings.auto_signature_help = Some(false);
11234                settings.show_signature_help_after_edits = Some(false);
11235            });
11236        });
11237    });
11238
11239    let mut cx = EditorLspTestContext::new_rust(
11240        lsp::ServerCapabilities {
11241            signature_help_provider: Some(lsp::SignatureHelpOptions {
11242                ..Default::default()
11243            }),
11244            ..Default::default()
11245        },
11246        cx,
11247    )
11248    .await;
11249
11250    let language = Language::new(
11251        LanguageConfig {
11252            name: "Rust".into(),
11253            brackets: BracketPairConfig {
11254                pairs: vec![
11255                    BracketPair {
11256                        start: "{".to_string(),
11257                        end: "}".to_string(),
11258                        close: true,
11259                        surround: true,
11260                        newline: true,
11261                    },
11262                    BracketPair {
11263                        start: "(".to_string(),
11264                        end: ")".to_string(),
11265                        close: true,
11266                        surround: true,
11267                        newline: true,
11268                    },
11269                    BracketPair {
11270                        start: "/*".to_string(),
11271                        end: " */".to_string(),
11272                        close: true,
11273                        surround: true,
11274                        newline: true,
11275                    },
11276                    BracketPair {
11277                        start: "[".to_string(),
11278                        end: "]".to_string(),
11279                        close: false,
11280                        surround: false,
11281                        newline: true,
11282                    },
11283                    BracketPair {
11284                        start: "\"".to_string(),
11285                        end: "\"".to_string(),
11286                        close: true,
11287                        surround: true,
11288                        newline: false,
11289                    },
11290                    BracketPair {
11291                        start: "<".to_string(),
11292                        end: ">".to_string(),
11293                        close: false,
11294                        surround: true,
11295                        newline: true,
11296                    },
11297                ],
11298                ..Default::default()
11299            },
11300            autoclose_before: "})]".to_string(),
11301            ..Default::default()
11302        },
11303        Some(tree_sitter_rust::LANGUAGE.into()),
11304    );
11305    let language = Arc::new(language);
11306
11307    cx.language_registry().add(language.clone());
11308    cx.update_buffer(|buffer, cx| {
11309        buffer.set_language(Some(language), cx);
11310    });
11311
11312    // Ensure that signature_help is not called when no signature help is enabled.
11313    cx.set_state(
11314        &r#"
11315            fn main() {
11316                sampleˇ
11317            }
11318        "#
11319        .unindent(),
11320    );
11321    cx.update_editor(|editor, window, cx| {
11322        editor.handle_input("(", window, cx);
11323    });
11324    cx.assert_editor_state(
11325        &"
11326            fn main() {
11327                sample(ˇ)
11328            }
11329        "
11330        .unindent(),
11331    );
11332    cx.editor(|editor, _, _| {
11333        assert!(editor.signature_help_state.task().is_none());
11334    });
11335
11336    let mocked_response = lsp::SignatureHelp {
11337        signatures: vec![lsp::SignatureInformation {
11338            label: "fn sample(param1: u8, param2: u8)".to_string(),
11339            documentation: None,
11340            parameters: Some(vec![
11341                lsp::ParameterInformation {
11342                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11343                    documentation: None,
11344                },
11345                lsp::ParameterInformation {
11346                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11347                    documentation: None,
11348                },
11349            ]),
11350            active_parameter: None,
11351        }],
11352        active_signature: Some(0),
11353        active_parameter: Some(0),
11354    };
11355
11356    // Ensure that signature_help is called when enabled afte edits
11357    cx.update(|_, cx| {
11358        cx.update_global::<SettingsStore, _>(|settings, cx| {
11359            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11360                settings.auto_signature_help = Some(false);
11361                settings.show_signature_help_after_edits = Some(true);
11362            });
11363        });
11364    });
11365    cx.set_state(
11366        &r#"
11367            fn main() {
11368                sampleˇ
11369            }
11370        "#
11371        .unindent(),
11372    );
11373    cx.update_editor(|editor, window, cx| {
11374        editor.handle_input("(", window, cx);
11375    });
11376    cx.assert_editor_state(
11377        &"
11378            fn main() {
11379                sample(ˇ)
11380            }
11381        "
11382        .unindent(),
11383    );
11384    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11385    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11386        .await;
11387    cx.update_editor(|editor, _, _| {
11388        let signature_help_state = editor.signature_help_state.popover().cloned();
11389        assert!(signature_help_state.is_some());
11390        let signature = signature_help_state.unwrap();
11391        assert_eq!(
11392            signature.signatures[signature.current_signature].label,
11393            "fn sample(param1: u8, param2: u8)"
11394        );
11395        editor.signature_help_state = SignatureHelpState::default();
11396    });
11397
11398    // Ensure that signature_help is called when auto signature help override is enabled
11399    cx.update(|_, cx| {
11400        cx.update_global::<SettingsStore, _>(|settings, cx| {
11401            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11402                settings.auto_signature_help = Some(true);
11403                settings.show_signature_help_after_edits = Some(false);
11404            });
11405        });
11406    });
11407    cx.set_state(
11408        &r#"
11409            fn main() {
11410                sampleˇ
11411            }
11412        "#
11413        .unindent(),
11414    );
11415    cx.update_editor(|editor, window, cx| {
11416        editor.handle_input("(", window, cx);
11417    });
11418    cx.assert_editor_state(
11419        &"
11420            fn main() {
11421                sample(ˇ)
11422            }
11423        "
11424        .unindent(),
11425    );
11426    handle_signature_help_request(&mut cx, mocked_response).await;
11427    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11428        .await;
11429    cx.editor(|editor, _, _| {
11430        let signature_help_state = editor.signature_help_state.popover().cloned();
11431        assert!(signature_help_state.is_some());
11432        let signature = signature_help_state.unwrap();
11433        assert_eq!(
11434            signature.signatures[signature.current_signature].label,
11435            "fn sample(param1: u8, param2: u8)"
11436        );
11437    });
11438}
11439
11440#[gpui::test]
11441async fn test_signature_help(cx: &mut TestAppContext) {
11442    init_test(cx, |_| {});
11443    cx.update(|cx| {
11444        cx.update_global::<SettingsStore, _>(|settings, cx| {
11445            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11446                settings.auto_signature_help = Some(true);
11447            });
11448        });
11449    });
11450
11451    let mut cx = EditorLspTestContext::new_rust(
11452        lsp::ServerCapabilities {
11453            signature_help_provider: Some(lsp::SignatureHelpOptions {
11454                ..Default::default()
11455            }),
11456            ..Default::default()
11457        },
11458        cx,
11459    )
11460    .await;
11461
11462    // A test that directly calls `show_signature_help`
11463    cx.update_editor(|editor, window, cx| {
11464        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11465    });
11466
11467    let mocked_response = lsp::SignatureHelp {
11468        signatures: vec![lsp::SignatureInformation {
11469            label: "fn sample(param1: u8, param2: u8)".to_string(),
11470            documentation: None,
11471            parameters: Some(vec![
11472                lsp::ParameterInformation {
11473                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11474                    documentation: None,
11475                },
11476                lsp::ParameterInformation {
11477                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11478                    documentation: None,
11479                },
11480            ]),
11481            active_parameter: None,
11482        }],
11483        active_signature: Some(0),
11484        active_parameter: Some(0),
11485    };
11486    handle_signature_help_request(&mut cx, mocked_response).await;
11487
11488    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11489        .await;
11490
11491    cx.editor(|editor, _, _| {
11492        let signature_help_state = editor.signature_help_state.popover().cloned();
11493        assert!(signature_help_state.is_some());
11494        let signature = signature_help_state.unwrap();
11495        assert_eq!(
11496            signature.signatures[signature.current_signature].label,
11497            "fn sample(param1: u8, param2: u8)"
11498        );
11499    });
11500
11501    // When exiting outside from inside the brackets, `signature_help` is closed.
11502    cx.set_state(indoc! {"
11503        fn main() {
11504            sample(ˇ);
11505        }
11506
11507        fn sample(param1: u8, param2: u8) {}
11508    "});
11509
11510    cx.update_editor(|editor, window, cx| {
11511        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11512            s.select_ranges([0..0])
11513        });
11514    });
11515
11516    let mocked_response = lsp::SignatureHelp {
11517        signatures: Vec::new(),
11518        active_signature: None,
11519        active_parameter: None,
11520    };
11521    handle_signature_help_request(&mut cx, mocked_response).await;
11522
11523    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11524        .await;
11525
11526    cx.editor(|editor, _, _| {
11527        assert!(!editor.signature_help_state.is_shown());
11528    });
11529
11530    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11531    cx.set_state(indoc! {"
11532        fn main() {
11533            sample(ˇ);
11534        }
11535
11536        fn sample(param1: u8, param2: u8) {}
11537    "});
11538
11539    let mocked_response = lsp::SignatureHelp {
11540        signatures: vec![lsp::SignatureInformation {
11541            label: "fn sample(param1: u8, param2: u8)".to_string(),
11542            documentation: None,
11543            parameters: Some(vec![
11544                lsp::ParameterInformation {
11545                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11546                    documentation: None,
11547                },
11548                lsp::ParameterInformation {
11549                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11550                    documentation: None,
11551                },
11552            ]),
11553            active_parameter: None,
11554        }],
11555        active_signature: Some(0),
11556        active_parameter: Some(0),
11557    };
11558    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11559    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11560        .await;
11561    cx.editor(|editor, _, _| {
11562        assert!(editor.signature_help_state.is_shown());
11563    });
11564
11565    // Restore the popover with more parameter input
11566    cx.set_state(indoc! {"
11567        fn main() {
11568            sample(param1, param2ˇ);
11569        }
11570
11571        fn sample(param1: u8, param2: u8) {}
11572    "});
11573
11574    let mocked_response = lsp::SignatureHelp {
11575        signatures: vec![lsp::SignatureInformation {
11576            label: "fn sample(param1: u8, param2: u8)".to_string(),
11577            documentation: None,
11578            parameters: Some(vec![
11579                lsp::ParameterInformation {
11580                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11581                    documentation: None,
11582                },
11583                lsp::ParameterInformation {
11584                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11585                    documentation: None,
11586                },
11587            ]),
11588            active_parameter: None,
11589        }],
11590        active_signature: Some(0),
11591        active_parameter: Some(1),
11592    };
11593    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11594    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11595        .await;
11596
11597    // When selecting a range, the popover is gone.
11598    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11599    cx.update_editor(|editor, window, cx| {
11600        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11601            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11602        })
11603    });
11604    cx.assert_editor_state(indoc! {"
11605        fn main() {
11606            sample(param1, «ˇparam2»);
11607        }
11608
11609        fn sample(param1: u8, param2: u8) {}
11610    "});
11611    cx.editor(|editor, _, _| {
11612        assert!(!editor.signature_help_state.is_shown());
11613    });
11614
11615    // When unselecting again, the popover is back if within the brackets.
11616    cx.update_editor(|editor, window, cx| {
11617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11618            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11619        })
11620    });
11621    cx.assert_editor_state(indoc! {"
11622        fn main() {
11623            sample(param1, ˇparam2);
11624        }
11625
11626        fn sample(param1: u8, param2: u8) {}
11627    "});
11628    handle_signature_help_request(&mut cx, mocked_response).await;
11629    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11630        .await;
11631    cx.editor(|editor, _, _| {
11632        assert!(editor.signature_help_state.is_shown());
11633    });
11634
11635    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11636    cx.update_editor(|editor, window, cx| {
11637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11638            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11639            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11640        })
11641    });
11642    cx.assert_editor_state(indoc! {"
11643        fn main() {
11644            sample(param1, ˇparam2);
11645        }
11646
11647        fn sample(param1: u8, param2: u8) {}
11648    "});
11649
11650    let mocked_response = lsp::SignatureHelp {
11651        signatures: vec![lsp::SignatureInformation {
11652            label: "fn sample(param1: u8, param2: u8)".to_string(),
11653            documentation: None,
11654            parameters: Some(vec![
11655                lsp::ParameterInformation {
11656                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11657                    documentation: None,
11658                },
11659                lsp::ParameterInformation {
11660                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11661                    documentation: None,
11662                },
11663            ]),
11664            active_parameter: None,
11665        }],
11666        active_signature: Some(0),
11667        active_parameter: Some(1),
11668    };
11669    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11670    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11671        .await;
11672    cx.update_editor(|editor, _, cx| {
11673        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11674    });
11675    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11676        .await;
11677    cx.update_editor(|editor, window, cx| {
11678        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11679            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11680        })
11681    });
11682    cx.assert_editor_state(indoc! {"
11683        fn main() {
11684            sample(param1, «ˇparam2»);
11685        }
11686
11687        fn sample(param1: u8, param2: u8) {}
11688    "});
11689    cx.update_editor(|editor, window, cx| {
11690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11691            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11692        })
11693    });
11694    cx.assert_editor_state(indoc! {"
11695        fn main() {
11696            sample(param1, ˇparam2);
11697        }
11698
11699        fn sample(param1: u8, param2: u8) {}
11700    "});
11701    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11702        .await;
11703}
11704
11705#[gpui::test]
11706async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11707    init_test(cx, |_| {});
11708
11709    let mut cx = EditorLspTestContext::new_rust(
11710        lsp::ServerCapabilities {
11711            signature_help_provider: Some(lsp::SignatureHelpOptions {
11712                ..Default::default()
11713            }),
11714            ..Default::default()
11715        },
11716        cx,
11717    )
11718    .await;
11719
11720    cx.set_state(indoc! {"
11721        fn main() {
11722            overloadedˇ
11723        }
11724    "});
11725
11726    cx.update_editor(|editor, window, cx| {
11727        editor.handle_input("(", window, cx);
11728        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11729    });
11730
11731    // Mock response with 3 signatures
11732    let mocked_response = lsp::SignatureHelp {
11733        signatures: vec![
11734            lsp::SignatureInformation {
11735                label: "fn overloaded(x: i32)".to_string(),
11736                documentation: None,
11737                parameters: Some(vec![lsp::ParameterInformation {
11738                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11739                    documentation: None,
11740                }]),
11741                active_parameter: None,
11742            },
11743            lsp::SignatureInformation {
11744                label: "fn overloaded(x: i32, y: i32)".to_string(),
11745                documentation: None,
11746                parameters: Some(vec![
11747                    lsp::ParameterInformation {
11748                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11749                        documentation: None,
11750                    },
11751                    lsp::ParameterInformation {
11752                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11753                        documentation: None,
11754                    },
11755                ]),
11756                active_parameter: None,
11757            },
11758            lsp::SignatureInformation {
11759                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11760                documentation: None,
11761                parameters: Some(vec![
11762                    lsp::ParameterInformation {
11763                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11764                        documentation: None,
11765                    },
11766                    lsp::ParameterInformation {
11767                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11768                        documentation: None,
11769                    },
11770                    lsp::ParameterInformation {
11771                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11772                        documentation: None,
11773                    },
11774                ]),
11775                active_parameter: None,
11776            },
11777        ],
11778        active_signature: Some(1),
11779        active_parameter: Some(0),
11780    };
11781    handle_signature_help_request(&mut cx, mocked_response).await;
11782
11783    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11784        .await;
11785
11786    // Verify we have multiple signatures and the right one is selected
11787    cx.editor(|editor, _, _| {
11788        let popover = editor.signature_help_state.popover().cloned().unwrap();
11789        assert_eq!(popover.signatures.len(), 3);
11790        // active_signature was 1, so that should be the current
11791        assert_eq!(popover.current_signature, 1);
11792        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11793        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11794        assert_eq!(
11795            popover.signatures[2].label,
11796            "fn overloaded(x: i32, y: i32, z: i32)"
11797        );
11798    });
11799
11800    // Test navigation functionality
11801    cx.update_editor(|editor, window, cx| {
11802        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11803    });
11804
11805    cx.editor(|editor, _, _| {
11806        let popover = editor.signature_help_state.popover().cloned().unwrap();
11807        assert_eq!(popover.current_signature, 2);
11808    });
11809
11810    // Test wrap around
11811    cx.update_editor(|editor, window, cx| {
11812        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11813    });
11814
11815    cx.editor(|editor, _, _| {
11816        let popover = editor.signature_help_state.popover().cloned().unwrap();
11817        assert_eq!(popover.current_signature, 0);
11818    });
11819
11820    // Test previous navigation
11821    cx.update_editor(|editor, window, cx| {
11822        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11823    });
11824
11825    cx.editor(|editor, _, _| {
11826        let popover = editor.signature_help_state.popover().cloned().unwrap();
11827        assert_eq!(popover.current_signature, 2);
11828    });
11829}
11830
11831#[gpui::test]
11832async fn test_completion_mode(cx: &mut TestAppContext) {
11833    init_test(cx, |_| {});
11834    let mut cx = EditorLspTestContext::new_rust(
11835        lsp::ServerCapabilities {
11836            completion_provider: Some(lsp::CompletionOptions {
11837                resolve_provider: Some(true),
11838                ..Default::default()
11839            }),
11840            ..Default::default()
11841        },
11842        cx,
11843    )
11844    .await;
11845
11846    struct Run {
11847        run_description: &'static str,
11848        initial_state: String,
11849        buffer_marked_text: String,
11850        completion_label: &'static str,
11851        completion_text: &'static str,
11852        expected_with_insert_mode: String,
11853        expected_with_replace_mode: String,
11854        expected_with_replace_subsequence_mode: String,
11855        expected_with_replace_suffix_mode: String,
11856    }
11857
11858    let runs = [
11859        Run {
11860            run_description: "Start of word matches completion text",
11861            initial_state: "before ediˇ after".into(),
11862            buffer_marked_text: "before <edi|> after".into(),
11863            completion_label: "editor",
11864            completion_text: "editor",
11865            expected_with_insert_mode: "before editorˇ after".into(),
11866            expected_with_replace_mode: "before editorˇ after".into(),
11867            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11868            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11869        },
11870        Run {
11871            run_description: "Accept same text at the middle of the word",
11872            initial_state: "before ediˇtor after".into(),
11873            buffer_marked_text: "before <edi|tor> after".into(),
11874            completion_label: "editor",
11875            completion_text: "editor",
11876            expected_with_insert_mode: "before editorˇtor after".into(),
11877            expected_with_replace_mode: "before editorˇ after".into(),
11878            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11879            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11880        },
11881        Run {
11882            run_description: "End of word matches completion text -- cursor at end",
11883            initial_state: "before torˇ after".into(),
11884            buffer_marked_text: "before <tor|> after".into(),
11885            completion_label: "editor",
11886            completion_text: "editor",
11887            expected_with_insert_mode: "before editorˇ after".into(),
11888            expected_with_replace_mode: "before editorˇ after".into(),
11889            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11890            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11891        },
11892        Run {
11893            run_description: "End of word matches completion text -- cursor at start",
11894            initial_state: "before ˇtor after".into(),
11895            buffer_marked_text: "before <|tor> after".into(),
11896            completion_label: "editor",
11897            completion_text: "editor",
11898            expected_with_insert_mode: "before editorˇtor after".into(),
11899            expected_with_replace_mode: "before editorˇ after".into(),
11900            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11901            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11902        },
11903        Run {
11904            run_description: "Prepend text containing whitespace",
11905            initial_state: "pˇfield: bool".into(),
11906            buffer_marked_text: "<p|field>: bool".into(),
11907            completion_label: "pub ",
11908            completion_text: "pub ",
11909            expected_with_insert_mode: "pub ˇfield: bool".into(),
11910            expected_with_replace_mode: "pub ˇ: bool".into(),
11911            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11912            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11913        },
11914        Run {
11915            run_description: "Add element to start of list",
11916            initial_state: "[element_ˇelement_2]".into(),
11917            buffer_marked_text: "[<element_|element_2>]".into(),
11918            completion_label: "element_1",
11919            completion_text: "element_1",
11920            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11921            expected_with_replace_mode: "[element_1ˇ]".into(),
11922            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11923            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11924        },
11925        Run {
11926            run_description: "Add element to start of list -- first and second elements are equal",
11927            initial_state: "[elˇelement]".into(),
11928            buffer_marked_text: "[<el|element>]".into(),
11929            completion_label: "element",
11930            completion_text: "element",
11931            expected_with_insert_mode: "[elementˇelement]".into(),
11932            expected_with_replace_mode: "[elementˇ]".into(),
11933            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11934            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11935        },
11936        Run {
11937            run_description: "Ends with matching suffix",
11938            initial_state: "SubˇError".into(),
11939            buffer_marked_text: "<Sub|Error>".into(),
11940            completion_label: "SubscriptionError",
11941            completion_text: "SubscriptionError",
11942            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11943            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11944            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11945            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11946        },
11947        Run {
11948            run_description: "Suffix is a subsequence -- contiguous",
11949            initial_state: "SubˇErr".into(),
11950            buffer_marked_text: "<Sub|Err>".into(),
11951            completion_label: "SubscriptionError",
11952            completion_text: "SubscriptionError",
11953            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11954            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11955            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11956            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11957        },
11958        Run {
11959            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11960            initial_state: "Suˇscrirr".into(),
11961            buffer_marked_text: "<Su|scrirr>".into(),
11962            completion_label: "SubscriptionError",
11963            completion_text: "SubscriptionError",
11964            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11965            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11966            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11967            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11968        },
11969        Run {
11970            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11971            initial_state: "foo(indˇix)".into(),
11972            buffer_marked_text: "foo(<ind|ix>)".into(),
11973            completion_label: "node_index",
11974            completion_text: "node_index",
11975            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11976            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11977            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11978            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11979        },
11980        Run {
11981            run_description: "Replace range ends before cursor - should extend to cursor",
11982            initial_state: "before editˇo after".into(),
11983            buffer_marked_text: "before <{ed}>it|o after".into(),
11984            completion_label: "editor",
11985            completion_text: "editor",
11986            expected_with_insert_mode: "before editorˇo after".into(),
11987            expected_with_replace_mode: "before editorˇo after".into(),
11988            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11989            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11990        },
11991        Run {
11992            run_description: "Uses label for suffix matching",
11993            initial_state: "before ediˇtor after".into(),
11994            buffer_marked_text: "before <edi|tor> after".into(),
11995            completion_label: "editor",
11996            completion_text: "editor()",
11997            expected_with_insert_mode: "before editor()ˇtor after".into(),
11998            expected_with_replace_mode: "before editor()ˇ after".into(),
11999            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12000            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12001        },
12002        Run {
12003            run_description: "Case insensitive subsequence and suffix matching",
12004            initial_state: "before EDiˇtoR after".into(),
12005            buffer_marked_text: "before <EDi|toR> after".into(),
12006            completion_label: "editor",
12007            completion_text: "editor",
12008            expected_with_insert_mode: "before editorˇtoR after".into(),
12009            expected_with_replace_mode: "before editorˇ after".into(),
12010            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12011            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12012        },
12013    ];
12014
12015    for run in runs {
12016        let run_variations = [
12017            (LspInsertMode::Insert, run.expected_with_insert_mode),
12018            (LspInsertMode::Replace, run.expected_with_replace_mode),
12019            (
12020                LspInsertMode::ReplaceSubsequence,
12021                run.expected_with_replace_subsequence_mode,
12022            ),
12023            (
12024                LspInsertMode::ReplaceSuffix,
12025                run.expected_with_replace_suffix_mode,
12026            ),
12027        ];
12028
12029        for (lsp_insert_mode, expected_text) in run_variations {
12030            eprintln!(
12031                "run = {:?}, mode = {lsp_insert_mode:.?}",
12032                run.run_description,
12033            );
12034
12035            update_test_language_settings(&mut cx, |settings| {
12036                settings.defaults.completions = Some(CompletionSettings {
12037                    lsp_insert_mode,
12038                    words: WordsCompletionMode::Disabled,
12039                    lsp: true,
12040                    lsp_fetch_timeout_ms: 0,
12041                });
12042            });
12043
12044            cx.set_state(&run.initial_state);
12045            cx.update_editor(|editor, window, cx| {
12046                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12047            });
12048
12049            let counter = Arc::new(AtomicUsize::new(0));
12050            handle_completion_request_with_insert_and_replace(
12051                &mut cx,
12052                &run.buffer_marked_text,
12053                vec![(run.completion_label, run.completion_text)],
12054                counter.clone(),
12055            )
12056            .await;
12057            cx.condition(|editor, _| editor.context_menu_visible())
12058                .await;
12059            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12060
12061            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12062                editor
12063                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12064                    .unwrap()
12065            });
12066            cx.assert_editor_state(&expected_text);
12067            handle_resolve_completion_request(&mut cx, None).await;
12068            apply_additional_edits.await.unwrap();
12069        }
12070    }
12071}
12072
12073#[gpui::test]
12074async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12075    init_test(cx, |_| {});
12076    let mut cx = EditorLspTestContext::new_rust(
12077        lsp::ServerCapabilities {
12078            completion_provider: Some(lsp::CompletionOptions {
12079                resolve_provider: Some(true),
12080                ..Default::default()
12081            }),
12082            ..Default::default()
12083        },
12084        cx,
12085    )
12086    .await;
12087
12088    let initial_state = "SubˇError";
12089    let buffer_marked_text = "<Sub|Error>";
12090    let completion_text = "SubscriptionError";
12091    let expected_with_insert_mode = "SubscriptionErrorˇError";
12092    let expected_with_replace_mode = "SubscriptionErrorˇ";
12093
12094    update_test_language_settings(&mut cx, |settings| {
12095        settings.defaults.completions = Some(CompletionSettings {
12096            words: WordsCompletionMode::Disabled,
12097            // set the opposite here to ensure that the action is overriding the default behavior
12098            lsp_insert_mode: LspInsertMode::Insert,
12099            lsp: true,
12100            lsp_fetch_timeout_ms: 0,
12101        });
12102    });
12103
12104    cx.set_state(initial_state);
12105    cx.update_editor(|editor, window, cx| {
12106        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12107    });
12108
12109    let counter = Arc::new(AtomicUsize::new(0));
12110    handle_completion_request_with_insert_and_replace(
12111        &mut cx,
12112        &buffer_marked_text,
12113        vec![(completion_text, completion_text)],
12114        counter.clone(),
12115    )
12116    .await;
12117    cx.condition(|editor, _| editor.context_menu_visible())
12118        .await;
12119    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12120
12121    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12122        editor
12123            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12124            .unwrap()
12125    });
12126    cx.assert_editor_state(&expected_with_replace_mode);
12127    handle_resolve_completion_request(&mut cx, None).await;
12128    apply_additional_edits.await.unwrap();
12129
12130    update_test_language_settings(&mut cx, |settings| {
12131        settings.defaults.completions = Some(CompletionSettings {
12132            words: WordsCompletionMode::Disabled,
12133            // set the opposite here to ensure that the action is overriding the default behavior
12134            lsp_insert_mode: LspInsertMode::Replace,
12135            lsp: true,
12136            lsp_fetch_timeout_ms: 0,
12137        });
12138    });
12139
12140    cx.set_state(initial_state);
12141    cx.update_editor(|editor, window, cx| {
12142        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12143    });
12144    handle_completion_request_with_insert_and_replace(
12145        &mut cx,
12146        &buffer_marked_text,
12147        vec![(completion_text, completion_text)],
12148        counter.clone(),
12149    )
12150    .await;
12151    cx.condition(|editor, _| editor.context_menu_visible())
12152        .await;
12153    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12154
12155    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12156        editor
12157            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12158            .unwrap()
12159    });
12160    cx.assert_editor_state(&expected_with_insert_mode);
12161    handle_resolve_completion_request(&mut cx, None).await;
12162    apply_additional_edits.await.unwrap();
12163}
12164
12165#[gpui::test]
12166async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12167    init_test(cx, |_| {});
12168    let mut cx = EditorLspTestContext::new_rust(
12169        lsp::ServerCapabilities {
12170            completion_provider: Some(lsp::CompletionOptions {
12171                resolve_provider: Some(true),
12172                ..Default::default()
12173            }),
12174            ..Default::default()
12175        },
12176        cx,
12177    )
12178    .await;
12179
12180    // scenario: surrounding text matches completion text
12181    let completion_text = "to_offset";
12182    let initial_state = indoc! {"
12183        1. buf.to_offˇsuffix
12184        2. buf.to_offˇsuf
12185        3. buf.to_offˇfix
12186        4. buf.to_offˇ
12187        5. into_offˇensive
12188        6. ˇsuffix
12189        7. let ˇ //
12190        8. aaˇzz
12191        9. buf.to_off«zzzzzˇ»suffix
12192        10. buf.«ˇzzzzz»suffix
12193        11. to_off«ˇzzzzz»
12194
12195        buf.to_offˇsuffix  // newest cursor
12196    "};
12197    let completion_marked_buffer = indoc! {"
12198        1. buf.to_offsuffix
12199        2. buf.to_offsuf
12200        3. buf.to_offfix
12201        4. buf.to_off
12202        5. into_offensive
12203        6. suffix
12204        7. let  //
12205        8. aazz
12206        9. buf.to_offzzzzzsuffix
12207        10. buf.zzzzzsuffix
12208        11. to_offzzzzz
12209
12210        buf.<to_off|suffix>  // newest cursor
12211    "};
12212    let expected = indoc! {"
12213        1. buf.to_offsetˇ
12214        2. buf.to_offsetˇsuf
12215        3. buf.to_offsetˇfix
12216        4. buf.to_offsetˇ
12217        5. into_offsetˇensive
12218        6. to_offsetˇsuffix
12219        7. let to_offsetˇ //
12220        8. aato_offsetˇzz
12221        9. buf.to_offsetˇ
12222        10. buf.to_offsetˇsuffix
12223        11. to_offsetˇ
12224
12225        buf.to_offsetˇ  // newest cursor
12226    "};
12227    cx.set_state(initial_state);
12228    cx.update_editor(|editor, window, cx| {
12229        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12230    });
12231    handle_completion_request_with_insert_and_replace(
12232        &mut cx,
12233        completion_marked_buffer,
12234        vec![(completion_text, completion_text)],
12235        Arc::new(AtomicUsize::new(0)),
12236    )
12237    .await;
12238    cx.condition(|editor, _| editor.context_menu_visible())
12239        .await;
12240    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12241        editor
12242            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12243            .unwrap()
12244    });
12245    cx.assert_editor_state(expected);
12246    handle_resolve_completion_request(&mut cx, None).await;
12247    apply_additional_edits.await.unwrap();
12248
12249    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12250    let completion_text = "foo_and_bar";
12251    let initial_state = indoc! {"
12252        1. ooanbˇ
12253        2. zooanbˇ
12254        3. ooanbˇz
12255        4. zooanbˇz
12256        5. ooanˇ
12257        6. oanbˇ
12258
12259        ooanbˇ
12260    "};
12261    let completion_marked_buffer = indoc! {"
12262        1. ooanb
12263        2. zooanb
12264        3. ooanbz
12265        4. zooanbz
12266        5. ooan
12267        6. oanb
12268
12269        <ooanb|>
12270    "};
12271    let expected = indoc! {"
12272        1. foo_and_barˇ
12273        2. zfoo_and_barˇ
12274        3. foo_and_barˇz
12275        4. zfoo_and_barˇz
12276        5. ooanfoo_and_barˇ
12277        6. oanbfoo_and_barˇ
12278
12279        foo_and_barˇ
12280    "};
12281    cx.set_state(initial_state);
12282    cx.update_editor(|editor, window, cx| {
12283        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12284    });
12285    handle_completion_request_with_insert_and_replace(
12286        &mut cx,
12287        completion_marked_buffer,
12288        vec![(completion_text, completion_text)],
12289        Arc::new(AtomicUsize::new(0)),
12290    )
12291    .await;
12292    cx.condition(|editor, _| editor.context_menu_visible())
12293        .await;
12294    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12295        editor
12296            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12297            .unwrap()
12298    });
12299    cx.assert_editor_state(expected);
12300    handle_resolve_completion_request(&mut cx, None).await;
12301    apply_additional_edits.await.unwrap();
12302
12303    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12304    // (expects the same as if it was inserted at the end)
12305    let completion_text = "foo_and_bar";
12306    let initial_state = indoc! {"
12307        1. ooˇanb
12308        2. zooˇanb
12309        3. ooˇanbz
12310        4. zooˇanbz
12311
12312        ooˇanb
12313    "};
12314    let completion_marked_buffer = indoc! {"
12315        1. ooanb
12316        2. zooanb
12317        3. ooanbz
12318        4. zooanbz
12319
12320        <oo|anb>
12321    "};
12322    let expected = indoc! {"
12323        1. foo_and_barˇ
12324        2. zfoo_and_barˇ
12325        3. foo_and_barˇz
12326        4. zfoo_and_barˇz
12327
12328        foo_and_barˇ
12329    "};
12330    cx.set_state(initial_state);
12331    cx.update_editor(|editor, window, cx| {
12332        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12333    });
12334    handle_completion_request_with_insert_and_replace(
12335        &mut cx,
12336        completion_marked_buffer,
12337        vec![(completion_text, completion_text)],
12338        Arc::new(AtomicUsize::new(0)),
12339    )
12340    .await;
12341    cx.condition(|editor, _| editor.context_menu_visible())
12342        .await;
12343    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12344        editor
12345            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12346            .unwrap()
12347    });
12348    cx.assert_editor_state(expected);
12349    handle_resolve_completion_request(&mut cx, None).await;
12350    apply_additional_edits.await.unwrap();
12351}
12352
12353// This used to crash
12354#[gpui::test]
12355async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12356    init_test(cx, |_| {});
12357
12358    let buffer_text = indoc! {"
12359        fn main() {
12360            10.satu;
12361
12362            //
12363            // separate cursors so they open in different excerpts (manually reproducible)
12364            //
12365
12366            10.satu20;
12367        }
12368    "};
12369    let multibuffer_text_with_selections = indoc! {"
12370        fn main() {
12371            10.satuˇ;
12372
12373            //
12374
12375            //
12376
12377            10.satuˇ20;
12378        }
12379    "};
12380    let expected_multibuffer = indoc! {"
12381        fn main() {
12382            10.saturating_sub()ˇ;
12383
12384            //
12385
12386            //
12387
12388            10.saturating_sub()ˇ;
12389        }
12390    "};
12391
12392    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12393    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12394
12395    let fs = FakeFs::new(cx.executor());
12396    fs.insert_tree(
12397        path!("/a"),
12398        json!({
12399            "main.rs": buffer_text,
12400        }),
12401    )
12402    .await;
12403
12404    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12405    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12406    language_registry.add(rust_lang());
12407    let mut fake_servers = language_registry.register_fake_lsp(
12408        "Rust",
12409        FakeLspAdapter {
12410            capabilities: lsp::ServerCapabilities {
12411                completion_provider: Some(lsp::CompletionOptions {
12412                    resolve_provider: None,
12413                    ..lsp::CompletionOptions::default()
12414                }),
12415                ..lsp::ServerCapabilities::default()
12416            },
12417            ..FakeLspAdapter::default()
12418        },
12419    );
12420    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12421    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12422    let buffer = project
12423        .update(cx, |project, cx| {
12424            project.open_local_buffer(path!("/a/main.rs"), cx)
12425        })
12426        .await
12427        .unwrap();
12428
12429    let multi_buffer = cx.new(|cx| {
12430        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12431        multi_buffer.push_excerpts(
12432            buffer.clone(),
12433            [ExcerptRange::new(0..first_excerpt_end)],
12434            cx,
12435        );
12436        multi_buffer.push_excerpts(
12437            buffer.clone(),
12438            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12439            cx,
12440        );
12441        multi_buffer
12442    });
12443
12444    let editor = workspace
12445        .update(cx, |_, window, cx| {
12446            cx.new(|cx| {
12447                Editor::new(
12448                    EditorMode::Full {
12449                        scale_ui_elements_with_buffer_font_size: false,
12450                        show_active_line_background: false,
12451                        sized_by_content: false,
12452                    },
12453                    multi_buffer.clone(),
12454                    Some(project.clone()),
12455                    window,
12456                    cx,
12457                )
12458            })
12459        })
12460        .unwrap();
12461
12462    let pane = workspace
12463        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12464        .unwrap();
12465    pane.update_in(cx, |pane, window, cx| {
12466        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12467    });
12468
12469    let fake_server = fake_servers.next().await.unwrap();
12470
12471    editor.update_in(cx, |editor, window, cx| {
12472        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12473            s.select_ranges([
12474                Point::new(1, 11)..Point::new(1, 11),
12475                Point::new(7, 11)..Point::new(7, 11),
12476            ])
12477        });
12478
12479        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12480    });
12481
12482    editor.update_in(cx, |editor, window, cx| {
12483        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12484    });
12485
12486    fake_server
12487        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12488            let completion_item = lsp::CompletionItem {
12489                label: "saturating_sub()".into(),
12490                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12491                    lsp::InsertReplaceEdit {
12492                        new_text: "saturating_sub()".to_owned(),
12493                        insert: lsp::Range::new(
12494                            lsp::Position::new(7, 7),
12495                            lsp::Position::new(7, 11),
12496                        ),
12497                        replace: lsp::Range::new(
12498                            lsp::Position::new(7, 7),
12499                            lsp::Position::new(7, 13),
12500                        ),
12501                    },
12502                )),
12503                ..lsp::CompletionItem::default()
12504            };
12505
12506            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12507        })
12508        .next()
12509        .await
12510        .unwrap();
12511
12512    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12513        .await;
12514
12515    editor
12516        .update_in(cx, |editor, window, cx| {
12517            editor
12518                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12519                .unwrap()
12520        })
12521        .await
12522        .unwrap();
12523
12524    editor.update(cx, |editor, cx| {
12525        assert_text_with_selections(editor, expected_multibuffer, cx);
12526    })
12527}
12528
12529#[gpui::test]
12530async fn test_completion(cx: &mut TestAppContext) {
12531    init_test(cx, |_| {});
12532
12533    let mut cx = EditorLspTestContext::new_rust(
12534        lsp::ServerCapabilities {
12535            completion_provider: Some(lsp::CompletionOptions {
12536                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12537                resolve_provider: Some(true),
12538                ..Default::default()
12539            }),
12540            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12541            ..Default::default()
12542        },
12543        cx,
12544    )
12545    .await;
12546    let counter = Arc::new(AtomicUsize::new(0));
12547
12548    cx.set_state(indoc! {"
12549        oneˇ
12550        two
12551        three
12552    "});
12553    cx.simulate_keystroke(".");
12554    handle_completion_request(
12555        indoc! {"
12556            one.|<>
12557            two
12558            three
12559        "},
12560        vec!["first_completion", "second_completion"],
12561        true,
12562        counter.clone(),
12563        &mut cx,
12564    )
12565    .await;
12566    cx.condition(|editor, _| editor.context_menu_visible())
12567        .await;
12568    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12569
12570    let _handler = handle_signature_help_request(
12571        &mut cx,
12572        lsp::SignatureHelp {
12573            signatures: vec![lsp::SignatureInformation {
12574                label: "test signature".to_string(),
12575                documentation: None,
12576                parameters: Some(vec![lsp::ParameterInformation {
12577                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12578                    documentation: None,
12579                }]),
12580                active_parameter: None,
12581            }],
12582            active_signature: None,
12583            active_parameter: None,
12584        },
12585    );
12586    cx.update_editor(|editor, window, cx| {
12587        assert!(
12588            !editor.signature_help_state.is_shown(),
12589            "No signature help was called for"
12590        );
12591        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12592    });
12593    cx.run_until_parked();
12594    cx.update_editor(|editor, _, _| {
12595        assert!(
12596            !editor.signature_help_state.is_shown(),
12597            "No signature help should be shown when completions menu is open"
12598        );
12599    });
12600
12601    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12602        editor.context_menu_next(&Default::default(), window, cx);
12603        editor
12604            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12605            .unwrap()
12606    });
12607    cx.assert_editor_state(indoc! {"
12608        one.second_completionˇ
12609        two
12610        three
12611    "});
12612
12613    handle_resolve_completion_request(
12614        &mut cx,
12615        Some(vec![
12616            (
12617                //This overlaps with the primary completion edit which is
12618                //misbehavior from the LSP spec, test that we filter it out
12619                indoc! {"
12620                    one.second_ˇcompletion
12621                    two
12622                    threeˇ
12623                "},
12624                "overlapping additional edit",
12625            ),
12626            (
12627                indoc! {"
12628                    one.second_completion
12629                    two
12630                    threeˇ
12631                "},
12632                "\nadditional edit",
12633            ),
12634        ]),
12635    )
12636    .await;
12637    apply_additional_edits.await.unwrap();
12638    cx.assert_editor_state(indoc! {"
12639        one.second_completionˇ
12640        two
12641        three
12642        additional edit
12643    "});
12644
12645    cx.set_state(indoc! {"
12646        one.second_completion
12647        twoˇ
12648        threeˇ
12649        additional edit
12650    "});
12651    cx.simulate_keystroke(" ");
12652    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12653    cx.simulate_keystroke("s");
12654    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12655
12656    cx.assert_editor_state(indoc! {"
12657        one.second_completion
12658        two sˇ
12659        three sˇ
12660        additional edit
12661    "});
12662    handle_completion_request(
12663        indoc! {"
12664            one.second_completion
12665            two s
12666            three <s|>
12667            additional edit
12668        "},
12669        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12670        true,
12671        counter.clone(),
12672        &mut cx,
12673    )
12674    .await;
12675    cx.condition(|editor, _| editor.context_menu_visible())
12676        .await;
12677    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12678
12679    cx.simulate_keystroke("i");
12680
12681    handle_completion_request(
12682        indoc! {"
12683            one.second_completion
12684            two si
12685            three <si|>
12686            additional edit
12687        "},
12688        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12689        true,
12690        counter.clone(),
12691        &mut cx,
12692    )
12693    .await;
12694    cx.condition(|editor, _| editor.context_menu_visible())
12695        .await;
12696    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12697
12698    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12699        editor
12700            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12701            .unwrap()
12702    });
12703    cx.assert_editor_state(indoc! {"
12704        one.second_completion
12705        two sixth_completionˇ
12706        three sixth_completionˇ
12707        additional edit
12708    "});
12709
12710    apply_additional_edits.await.unwrap();
12711
12712    update_test_language_settings(&mut cx, |settings| {
12713        settings.defaults.show_completions_on_input = Some(false);
12714    });
12715    cx.set_state("editorˇ");
12716    cx.simulate_keystroke(".");
12717    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12718    cx.simulate_keystrokes("c l o");
12719    cx.assert_editor_state("editor.cloˇ");
12720    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12721    cx.update_editor(|editor, window, cx| {
12722        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12723    });
12724    handle_completion_request(
12725        "editor.<clo|>",
12726        vec!["close", "clobber"],
12727        true,
12728        counter.clone(),
12729        &mut cx,
12730    )
12731    .await;
12732    cx.condition(|editor, _| editor.context_menu_visible())
12733        .await;
12734    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12735
12736    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12737        editor
12738            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12739            .unwrap()
12740    });
12741    cx.assert_editor_state("editor.clobberˇ");
12742    handle_resolve_completion_request(&mut cx, None).await;
12743    apply_additional_edits.await.unwrap();
12744}
12745
12746#[gpui::test]
12747async fn test_completion_reuse(cx: &mut TestAppContext) {
12748    init_test(cx, |_| {});
12749
12750    let mut cx = EditorLspTestContext::new_rust(
12751        lsp::ServerCapabilities {
12752            completion_provider: Some(lsp::CompletionOptions {
12753                trigger_characters: Some(vec![".".to_string()]),
12754                ..Default::default()
12755            }),
12756            ..Default::default()
12757        },
12758        cx,
12759    )
12760    .await;
12761
12762    let counter = Arc::new(AtomicUsize::new(0));
12763    cx.set_state("objˇ");
12764    cx.simulate_keystroke(".");
12765
12766    // Initial completion request returns complete results
12767    let is_incomplete = false;
12768    handle_completion_request(
12769        "obj.|<>",
12770        vec!["a", "ab", "abc"],
12771        is_incomplete,
12772        counter.clone(),
12773        &mut cx,
12774    )
12775    .await;
12776    cx.run_until_parked();
12777    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12778    cx.assert_editor_state("obj.ˇ");
12779    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12780
12781    // Type "a" - filters existing completions
12782    cx.simulate_keystroke("a");
12783    cx.run_until_parked();
12784    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12785    cx.assert_editor_state("obj.aˇ");
12786    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12787
12788    // Type "b" - filters existing completions
12789    cx.simulate_keystroke("b");
12790    cx.run_until_parked();
12791    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12792    cx.assert_editor_state("obj.abˇ");
12793    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12794
12795    // Type "c" - filters existing completions
12796    cx.simulate_keystroke("c");
12797    cx.run_until_parked();
12798    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12799    cx.assert_editor_state("obj.abcˇ");
12800    check_displayed_completions(vec!["abc"], &mut cx);
12801
12802    // Backspace to delete "c" - filters existing completions
12803    cx.update_editor(|editor, window, cx| {
12804        editor.backspace(&Backspace, window, cx);
12805    });
12806    cx.run_until_parked();
12807    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12808    cx.assert_editor_state("obj.abˇ");
12809    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12810
12811    // Moving cursor to the left dismisses menu.
12812    cx.update_editor(|editor, window, cx| {
12813        editor.move_left(&MoveLeft, window, cx);
12814    });
12815    cx.run_until_parked();
12816    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12817    cx.assert_editor_state("obj.aˇb");
12818    cx.update_editor(|editor, _, _| {
12819        assert_eq!(editor.context_menu_visible(), false);
12820    });
12821
12822    // Type "b" - new request
12823    cx.simulate_keystroke("b");
12824    let is_incomplete = false;
12825    handle_completion_request(
12826        "obj.<ab|>a",
12827        vec!["ab", "abc"],
12828        is_incomplete,
12829        counter.clone(),
12830        &mut cx,
12831    )
12832    .await;
12833    cx.run_until_parked();
12834    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12835    cx.assert_editor_state("obj.abˇb");
12836    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12837
12838    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12839    cx.update_editor(|editor, window, cx| {
12840        editor.backspace(&Backspace, window, cx);
12841    });
12842    let is_incomplete = false;
12843    handle_completion_request(
12844        "obj.<a|>b",
12845        vec!["a", "ab", "abc"],
12846        is_incomplete,
12847        counter.clone(),
12848        &mut cx,
12849    )
12850    .await;
12851    cx.run_until_parked();
12852    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12853    cx.assert_editor_state("obj.aˇb");
12854    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12855
12856    // Backspace to delete "a" - dismisses menu.
12857    cx.update_editor(|editor, window, cx| {
12858        editor.backspace(&Backspace, window, cx);
12859    });
12860    cx.run_until_parked();
12861    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12862    cx.assert_editor_state("obj.ˇb");
12863    cx.update_editor(|editor, _, _| {
12864        assert_eq!(editor.context_menu_visible(), false);
12865    });
12866}
12867
12868#[gpui::test]
12869async fn test_word_completion(cx: &mut TestAppContext) {
12870    let lsp_fetch_timeout_ms = 10;
12871    init_test(cx, |language_settings| {
12872        language_settings.defaults.completions = Some(CompletionSettings {
12873            words: WordsCompletionMode::Fallback,
12874            lsp: true,
12875            lsp_fetch_timeout_ms: 10,
12876            lsp_insert_mode: LspInsertMode::Insert,
12877        });
12878    });
12879
12880    let mut cx = EditorLspTestContext::new_rust(
12881        lsp::ServerCapabilities {
12882            completion_provider: Some(lsp::CompletionOptions {
12883                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12884                ..lsp::CompletionOptions::default()
12885            }),
12886            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12887            ..lsp::ServerCapabilities::default()
12888        },
12889        cx,
12890    )
12891    .await;
12892
12893    let throttle_completions = Arc::new(AtomicBool::new(false));
12894
12895    let lsp_throttle_completions = throttle_completions.clone();
12896    let _completion_requests_handler =
12897        cx.lsp
12898            .server
12899            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12900                let lsp_throttle_completions = lsp_throttle_completions.clone();
12901                let cx = cx.clone();
12902                async move {
12903                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12904                        cx.background_executor()
12905                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12906                            .await;
12907                    }
12908                    Ok(Some(lsp::CompletionResponse::Array(vec![
12909                        lsp::CompletionItem {
12910                            label: "first".into(),
12911                            ..lsp::CompletionItem::default()
12912                        },
12913                        lsp::CompletionItem {
12914                            label: "last".into(),
12915                            ..lsp::CompletionItem::default()
12916                        },
12917                    ])))
12918                }
12919            });
12920
12921    cx.set_state(indoc! {"
12922        oneˇ
12923        two
12924        three
12925    "});
12926    cx.simulate_keystroke(".");
12927    cx.executor().run_until_parked();
12928    cx.condition(|editor, _| editor.context_menu_visible())
12929        .await;
12930    cx.update_editor(|editor, window, cx| {
12931        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12932        {
12933            assert_eq!(
12934                completion_menu_entries(&menu),
12935                &["first", "last"],
12936                "When LSP server is fast to reply, no fallback word completions are used"
12937            );
12938        } else {
12939            panic!("expected completion menu to be open");
12940        }
12941        editor.cancel(&Cancel, window, cx);
12942    });
12943    cx.executor().run_until_parked();
12944    cx.condition(|editor, _| !editor.context_menu_visible())
12945        .await;
12946
12947    throttle_completions.store(true, atomic::Ordering::Release);
12948    cx.simulate_keystroke(".");
12949    cx.executor()
12950        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12951    cx.executor().run_until_parked();
12952    cx.condition(|editor, _| editor.context_menu_visible())
12953        .await;
12954    cx.update_editor(|editor, _, _| {
12955        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12956        {
12957            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12958                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12959        } else {
12960            panic!("expected completion menu to be open");
12961        }
12962    });
12963}
12964
12965#[gpui::test]
12966async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12967    init_test(cx, |language_settings| {
12968        language_settings.defaults.completions = Some(CompletionSettings {
12969            words: WordsCompletionMode::Enabled,
12970            lsp: true,
12971            lsp_fetch_timeout_ms: 0,
12972            lsp_insert_mode: LspInsertMode::Insert,
12973        });
12974    });
12975
12976    let mut cx = EditorLspTestContext::new_rust(
12977        lsp::ServerCapabilities {
12978            completion_provider: Some(lsp::CompletionOptions {
12979                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12980                ..lsp::CompletionOptions::default()
12981            }),
12982            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12983            ..lsp::ServerCapabilities::default()
12984        },
12985        cx,
12986    )
12987    .await;
12988
12989    let _completion_requests_handler =
12990        cx.lsp
12991            .server
12992            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12993                Ok(Some(lsp::CompletionResponse::Array(vec![
12994                    lsp::CompletionItem {
12995                        label: "first".into(),
12996                        ..lsp::CompletionItem::default()
12997                    },
12998                    lsp::CompletionItem {
12999                        label: "last".into(),
13000                        ..lsp::CompletionItem::default()
13001                    },
13002                ])))
13003            });
13004
13005    cx.set_state(indoc! {"ˇ
13006        first
13007        last
13008        second
13009    "});
13010    cx.simulate_keystroke(".");
13011    cx.executor().run_until_parked();
13012    cx.condition(|editor, _| editor.context_menu_visible())
13013        .await;
13014    cx.update_editor(|editor, _, _| {
13015        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13016        {
13017            assert_eq!(
13018                completion_menu_entries(&menu),
13019                &["first", "last", "second"],
13020                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13021            );
13022        } else {
13023            panic!("expected completion menu to be open");
13024        }
13025    });
13026}
13027
13028#[gpui::test]
13029async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13030    init_test(cx, |language_settings| {
13031        language_settings.defaults.completions = Some(CompletionSettings {
13032            words: WordsCompletionMode::Disabled,
13033            lsp: true,
13034            lsp_fetch_timeout_ms: 0,
13035            lsp_insert_mode: LspInsertMode::Insert,
13036        });
13037    });
13038
13039    let mut cx = EditorLspTestContext::new_rust(
13040        lsp::ServerCapabilities {
13041            completion_provider: Some(lsp::CompletionOptions {
13042                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13043                ..lsp::CompletionOptions::default()
13044            }),
13045            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13046            ..lsp::ServerCapabilities::default()
13047        },
13048        cx,
13049    )
13050    .await;
13051
13052    let _completion_requests_handler =
13053        cx.lsp
13054            .server
13055            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13056                panic!("LSP completions should not be queried when dealing with word completions")
13057            });
13058
13059    cx.set_state(indoc! {"ˇ
13060        first
13061        last
13062        second
13063    "});
13064    cx.update_editor(|editor, window, cx| {
13065        editor.show_word_completions(&ShowWordCompletions, window, cx);
13066    });
13067    cx.executor().run_until_parked();
13068    cx.condition(|editor, _| editor.context_menu_visible())
13069        .await;
13070    cx.update_editor(|editor, _, _| {
13071        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13072        {
13073            assert_eq!(
13074                completion_menu_entries(&menu),
13075                &["first", "last", "second"],
13076                "`ShowWordCompletions` action should show word completions"
13077            );
13078        } else {
13079            panic!("expected completion menu to be open");
13080        }
13081    });
13082
13083    cx.simulate_keystroke("l");
13084    cx.executor().run_until_parked();
13085    cx.condition(|editor, _| editor.context_menu_visible())
13086        .await;
13087    cx.update_editor(|editor, _, _| {
13088        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13089        {
13090            assert_eq!(
13091                completion_menu_entries(&menu),
13092                &["last"],
13093                "After showing word completions, further editing should filter them and not query the LSP"
13094            );
13095        } else {
13096            panic!("expected completion menu to be open");
13097        }
13098    });
13099}
13100
13101#[gpui::test]
13102async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13103    init_test(cx, |language_settings| {
13104        language_settings.defaults.completions = Some(CompletionSettings {
13105            words: WordsCompletionMode::Fallback,
13106            lsp: false,
13107            lsp_fetch_timeout_ms: 0,
13108            lsp_insert_mode: LspInsertMode::Insert,
13109        });
13110    });
13111
13112    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13113
13114    cx.set_state(indoc! {"ˇ
13115        0_usize
13116        let
13117        33
13118        4.5f32
13119    "});
13120    cx.update_editor(|editor, window, cx| {
13121        editor.show_completions(&ShowCompletions::default(), window, cx);
13122    });
13123    cx.executor().run_until_parked();
13124    cx.condition(|editor, _| editor.context_menu_visible())
13125        .await;
13126    cx.update_editor(|editor, window, cx| {
13127        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13128        {
13129            assert_eq!(
13130                completion_menu_entries(&menu),
13131                &["let"],
13132                "With no digits in the completion query, no digits should be in the word completions"
13133            );
13134        } else {
13135            panic!("expected completion menu to be open");
13136        }
13137        editor.cancel(&Cancel, window, cx);
13138    });
13139
13140    cx.set_state(indoc! {"13141        0_usize
13142        let
13143        3
13144        33.35f32
13145    "});
13146    cx.update_editor(|editor, window, cx| {
13147        editor.show_completions(&ShowCompletions::default(), window, cx);
13148    });
13149    cx.executor().run_until_parked();
13150    cx.condition(|editor, _| editor.context_menu_visible())
13151        .await;
13152    cx.update_editor(|editor, _, _| {
13153        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13154        {
13155            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13156                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13157        } else {
13158            panic!("expected completion menu to be open");
13159        }
13160    });
13161}
13162
13163fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13164    let position = || lsp::Position {
13165        line: params.text_document_position.position.line,
13166        character: params.text_document_position.position.character,
13167    };
13168    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13169        range: lsp::Range {
13170            start: position(),
13171            end: position(),
13172        },
13173        new_text: text.to_string(),
13174    }))
13175}
13176
13177#[gpui::test]
13178async fn test_multiline_completion(cx: &mut TestAppContext) {
13179    init_test(cx, |_| {});
13180
13181    let fs = FakeFs::new(cx.executor());
13182    fs.insert_tree(
13183        path!("/a"),
13184        json!({
13185            "main.ts": "a",
13186        }),
13187    )
13188    .await;
13189
13190    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13191    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13192    let typescript_language = Arc::new(Language::new(
13193        LanguageConfig {
13194            name: "TypeScript".into(),
13195            matcher: LanguageMatcher {
13196                path_suffixes: vec!["ts".to_string()],
13197                ..LanguageMatcher::default()
13198            },
13199            line_comments: vec!["// ".into()],
13200            ..LanguageConfig::default()
13201        },
13202        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13203    ));
13204    language_registry.add(typescript_language.clone());
13205    let mut fake_servers = language_registry.register_fake_lsp(
13206        "TypeScript",
13207        FakeLspAdapter {
13208            capabilities: lsp::ServerCapabilities {
13209                completion_provider: Some(lsp::CompletionOptions {
13210                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13211                    ..lsp::CompletionOptions::default()
13212                }),
13213                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13214                ..lsp::ServerCapabilities::default()
13215            },
13216            // Emulate vtsls label generation
13217            label_for_completion: Some(Box::new(|item, _| {
13218                let text = if let Some(description) = item
13219                    .label_details
13220                    .as_ref()
13221                    .and_then(|label_details| label_details.description.as_ref())
13222                {
13223                    format!("{} {}", item.label, description)
13224                } else if let Some(detail) = &item.detail {
13225                    format!("{} {}", item.label, detail)
13226                } else {
13227                    item.label.clone()
13228                };
13229                let len = text.len();
13230                Some(language::CodeLabel {
13231                    text,
13232                    runs: Vec::new(),
13233                    filter_range: 0..len,
13234                })
13235            })),
13236            ..FakeLspAdapter::default()
13237        },
13238    );
13239    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13240    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13241    let worktree_id = workspace
13242        .update(cx, |workspace, _window, cx| {
13243            workspace.project().update(cx, |project, cx| {
13244                project.worktrees(cx).next().unwrap().read(cx).id()
13245            })
13246        })
13247        .unwrap();
13248    let _buffer = project
13249        .update(cx, |project, cx| {
13250            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13251        })
13252        .await
13253        .unwrap();
13254    let editor = workspace
13255        .update(cx, |workspace, window, cx| {
13256            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13257        })
13258        .unwrap()
13259        .await
13260        .unwrap()
13261        .downcast::<Editor>()
13262        .unwrap();
13263    let fake_server = fake_servers.next().await.unwrap();
13264
13265    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13266    let multiline_label_2 = "a\nb\nc\n";
13267    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13268    let multiline_description = "d\ne\nf\n";
13269    let multiline_detail_2 = "g\nh\ni\n";
13270
13271    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13272        move |params, _| async move {
13273            Ok(Some(lsp::CompletionResponse::Array(vec![
13274                lsp::CompletionItem {
13275                    label: multiline_label.to_string(),
13276                    text_edit: gen_text_edit(&params, "new_text_1"),
13277                    ..lsp::CompletionItem::default()
13278                },
13279                lsp::CompletionItem {
13280                    label: "single line label 1".to_string(),
13281                    detail: Some(multiline_detail.to_string()),
13282                    text_edit: gen_text_edit(&params, "new_text_2"),
13283                    ..lsp::CompletionItem::default()
13284                },
13285                lsp::CompletionItem {
13286                    label: "single line label 2".to_string(),
13287                    label_details: Some(lsp::CompletionItemLabelDetails {
13288                        description: Some(multiline_description.to_string()),
13289                        detail: None,
13290                    }),
13291                    text_edit: gen_text_edit(&params, "new_text_2"),
13292                    ..lsp::CompletionItem::default()
13293                },
13294                lsp::CompletionItem {
13295                    label: multiline_label_2.to_string(),
13296                    detail: Some(multiline_detail_2.to_string()),
13297                    text_edit: gen_text_edit(&params, "new_text_3"),
13298                    ..lsp::CompletionItem::default()
13299                },
13300                lsp::CompletionItem {
13301                    label: "Label with many     spaces and \t but without newlines".to_string(),
13302                    detail: Some(
13303                        "Details with many     spaces and \t but without newlines".to_string(),
13304                    ),
13305                    text_edit: gen_text_edit(&params, "new_text_4"),
13306                    ..lsp::CompletionItem::default()
13307                },
13308            ])))
13309        },
13310    );
13311
13312    editor.update_in(cx, |editor, window, cx| {
13313        cx.focus_self(window);
13314        editor.move_to_end(&MoveToEnd, window, cx);
13315        editor.handle_input(".", window, cx);
13316    });
13317    cx.run_until_parked();
13318    completion_handle.next().await.unwrap();
13319
13320    editor.update(cx, |editor, _| {
13321        assert!(editor.context_menu_visible());
13322        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13323        {
13324            let completion_labels = menu
13325                .completions
13326                .borrow()
13327                .iter()
13328                .map(|c| c.label.text.clone())
13329                .collect::<Vec<_>>();
13330            assert_eq!(
13331                completion_labels,
13332                &[
13333                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13334                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13335                    "single line label 2 d e f ",
13336                    "a b c g h i ",
13337                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13338                ],
13339                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13340            );
13341
13342            for completion in menu
13343                .completions
13344                .borrow()
13345                .iter() {
13346                    assert_eq!(
13347                        completion.label.filter_range,
13348                        0..completion.label.text.len(),
13349                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13350                    );
13351                }
13352        } else {
13353            panic!("expected completion menu to be open");
13354        }
13355    });
13356}
13357
13358#[gpui::test]
13359async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13360    init_test(cx, |_| {});
13361    let mut cx = EditorLspTestContext::new_rust(
13362        lsp::ServerCapabilities {
13363            completion_provider: Some(lsp::CompletionOptions {
13364                trigger_characters: Some(vec![".".to_string()]),
13365                ..Default::default()
13366            }),
13367            ..Default::default()
13368        },
13369        cx,
13370    )
13371    .await;
13372    cx.lsp
13373        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13374            Ok(Some(lsp::CompletionResponse::Array(vec![
13375                lsp::CompletionItem {
13376                    label: "first".into(),
13377                    ..Default::default()
13378                },
13379                lsp::CompletionItem {
13380                    label: "last".into(),
13381                    ..Default::default()
13382                },
13383            ])))
13384        });
13385    cx.set_state("variableˇ");
13386    cx.simulate_keystroke(".");
13387    cx.executor().run_until_parked();
13388
13389    cx.update_editor(|editor, _, _| {
13390        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13391        {
13392            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13393        } else {
13394            panic!("expected completion menu to be open");
13395        }
13396    });
13397
13398    cx.update_editor(|editor, window, cx| {
13399        editor.move_page_down(&MovePageDown::default(), window, cx);
13400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13401        {
13402            assert!(
13403                menu.selected_item == 1,
13404                "expected PageDown to select the last item from the context menu"
13405            );
13406        } else {
13407            panic!("expected completion menu to stay open after PageDown");
13408        }
13409    });
13410
13411    cx.update_editor(|editor, window, cx| {
13412        editor.move_page_up(&MovePageUp::default(), window, cx);
13413        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13414        {
13415            assert!(
13416                menu.selected_item == 0,
13417                "expected PageUp to select the first item from the context menu"
13418            );
13419        } else {
13420            panic!("expected completion menu to stay open after PageUp");
13421        }
13422    });
13423}
13424
13425#[gpui::test]
13426async fn test_as_is_completions(cx: &mut TestAppContext) {
13427    init_test(cx, |_| {});
13428    let mut cx = EditorLspTestContext::new_rust(
13429        lsp::ServerCapabilities {
13430            completion_provider: Some(lsp::CompletionOptions {
13431                ..Default::default()
13432            }),
13433            ..Default::default()
13434        },
13435        cx,
13436    )
13437    .await;
13438    cx.lsp
13439        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13440            Ok(Some(lsp::CompletionResponse::Array(vec![
13441                lsp::CompletionItem {
13442                    label: "unsafe".into(),
13443                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13444                        range: lsp::Range {
13445                            start: lsp::Position {
13446                                line: 1,
13447                                character: 2,
13448                            },
13449                            end: lsp::Position {
13450                                line: 1,
13451                                character: 3,
13452                            },
13453                        },
13454                        new_text: "unsafe".to_string(),
13455                    })),
13456                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13457                    ..Default::default()
13458                },
13459            ])))
13460        });
13461    cx.set_state("fn a() {}\n");
13462    cx.executor().run_until_parked();
13463    cx.update_editor(|editor, window, cx| {
13464        editor.show_completions(
13465            &ShowCompletions {
13466                trigger: Some("\n".into()),
13467            },
13468            window,
13469            cx,
13470        );
13471    });
13472    cx.executor().run_until_parked();
13473
13474    cx.update_editor(|editor, window, cx| {
13475        editor.confirm_completion(&Default::default(), window, cx)
13476    });
13477    cx.executor().run_until_parked();
13478    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13479}
13480
13481#[gpui::test]
13482async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13483    init_test(cx, |_| {});
13484    let language =
13485        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13486    let mut cx = EditorLspTestContext::new(
13487        language,
13488        lsp::ServerCapabilities {
13489            completion_provider: Some(lsp::CompletionOptions {
13490                ..lsp::CompletionOptions::default()
13491            }),
13492            ..lsp::ServerCapabilities::default()
13493        },
13494        cx,
13495    )
13496    .await;
13497
13498    cx.set_state(
13499        "#ifndef BAR_H
13500#define BAR_H
13501
13502#include <stdbool.h>
13503
13504int fn_branch(bool do_branch1, bool do_branch2);
13505
13506#endif // BAR_H
13507ˇ",
13508    );
13509    cx.executor().run_until_parked();
13510    cx.update_editor(|editor, window, cx| {
13511        editor.handle_input("#", window, cx);
13512    });
13513    cx.executor().run_until_parked();
13514    cx.update_editor(|editor, window, cx| {
13515        editor.handle_input("i", window, cx);
13516    });
13517    cx.executor().run_until_parked();
13518    cx.update_editor(|editor, window, cx| {
13519        editor.handle_input("n", window, cx);
13520    });
13521    cx.executor().run_until_parked();
13522    cx.assert_editor_state(
13523        "#ifndef BAR_H
13524#define BAR_H
13525
13526#include <stdbool.h>
13527
13528int fn_branch(bool do_branch1, bool do_branch2);
13529
13530#endif // BAR_H
13531#inˇ",
13532    );
13533
13534    cx.lsp
13535        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13536            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13537                is_incomplete: false,
13538                item_defaults: None,
13539                items: vec![lsp::CompletionItem {
13540                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13541                    label_details: Some(lsp::CompletionItemLabelDetails {
13542                        detail: Some("header".to_string()),
13543                        description: None,
13544                    }),
13545                    label: " include".to_string(),
13546                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13547                        range: lsp::Range {
13548                            start: lsp::Position {
13549                                line: 8,
13550                                character: 1,
13551                            },
13552                            end: lsp::Position {
13553                                line: 8,
13554                                character: 1,
13555                            },
13556                        },
13557                        new_text: "include \"$0\"".to_string(),
13558                    })),
13559                    sort_text: Some("40b67681include".to_string()),
13560                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13561                    filter_text: Some("include".to_string()),
13562                    insert_text: Some("include \"$0\"".to_string()),
13563                    ..lsp::CompletionItem::default()
13564                }],
13565            })))
13566        });
13567    cx.update_editor(|editor, window, cx| {
13568        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13569    });
13570    cx.executor().run_until_parked();
13571    cx.update_editor(|editor, window, cx| {
13572        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13573    });
13574    cx.executor().run_until_parked();
13575    cx.assert_editor_state(
13576        "#ifndef BAR_H
13577#define BAR_H
13578
13579#include <stdbool.h>
13580
13581int fn_branch(bool do_branch1, bool do_branch2);
13582
13583#endif // BAR_H
13584#include \"ˇ\"",
13585    );
13586
13587    cx.lsp
13588        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13589            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13590                is_incomplete: true,
13591                item_defaults: None,
13592                items: vec![lsp::CompletionItem {
13593                    kind: Some(lsp::CompletionItemKind::FILE),
13594                    label: "AGL/".to_string(),
13595                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13596                        range: lsp::Range {
13597                            start: lsp::Position {
13598                                line: 8,
13599                                character: 10,
13600                            },
13601                            end: lsp::Position {
13602                                line: 8,
13603                                character: 11,
13604                            },
13605                        },
13606                        new_text: "AGL/".to_string(),
13607                    })),
13608                    sort_text: Some("40b67681AGL/".to_string()),
13609                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13610                    filter_text: Some("AGL/".to_string()),
13611                    insert_text: Some("AGL/".to_string()),
13612                    ..lsp::CompletionItem::default()
13613                }],
13614            })))
13615        });
13616    cx.update_editor(|editor, window, cx| {
13617        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13618    });
13619    cx.executor().run_until_parked();
13620    cx.update_editor(|editor, window, cx| {
13621        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13622    });
13623    cx.executor().run_until_parked();
13624    cx.assert_editor_state(
13625        r##"#ifndef BAR_H
13626#define BAR_H
13627
13628#include <stdbool.h>
13629
13630int fn_branch(bool do_branch1, bool do_branch2);
13631
13632#endif // BAR_H
13633#include "AGL/ˇ"##,
13634    );
13635
13636    cx.update_editor(|editor, window, cx| {
13637        editor.handle_input("\"", window, cx);
13638    });
13639    cx.executor().run_until_parked();
13640    cx.assert_editor_state(
13641        r##"#ifndef BAR_H
13642#define BAR_H
13643
13644#include <stdbool.h>
13645
13646int fn_branch(bool do_branch1, bool do_branch2);
13647
13648#endif // BAR_H
13649#include "AGL/"ˇ"##,
13650    );
13651}
13652
13653#[gpui::test]
13654async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13655    init_test(cx, |_| {});
13656
13657    let mut cx = EditorLspTestContext::new_rust(
13658        lsp::ServerCapabilities {
13659            completion_provider: Some(lsp::CompletionOptions {
13660                trigger_characters: Some(vec![".".to_string()]),
13661                resolve_provider: Some(true),
13662                ..Default::default()
13663            }),
13664            ..Default::default()
13665        },
13666        cx,
13667    )
13668    .await;
13669
13670    cx.set_state("fn main() { let a = 2ˇ; }");
13671    cx.simulate_keystroke(".");
13672    let completion_item = lsp::CompletionItem {
13673        label: "Some".into(),
13674        kind: Some(lsp::CompletionItemKind::SNIPPET),
13675        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13676        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13677            kind: lsp::MarkupKind::Markdown,
13678            value: "```rust\nSome(2)\n```".to_string(),
13679        })),
13680        deprecated: Some(false),
13681        sort_text: Some("Some".to_string()),
13682        filter_text: Some("Some".to_string()),
13683        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13684        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13685            range: lsp::Range {
13686                start: lsp::Position {
13687                    line: 0,
13688                    character: 22,
13689                },
13690                end: lsp::Position {
13691                    line: 0,
13692                    character: 22,
13693                },
13694            },
13695            new_text: "Some(2)".to_string(),
13696        })),
13697        additional_text_edits: Some(vec![lsp::TextEdit {
13698            range: lsp::Range {
13699                start: lsp::Position {
13700                    line: 0,
13701                    character: 20,
13702                },
13703                end: lsp::Position {
13704                    line: 0,
13705                    character: 22,
13706                },
13707            },
13708            new_text: "".to_string(),
13709        }]),
13710        ..Default::default()
13711    };
13712
13713    let closure_completion_item = completion_item.clone();
13714    let counter = Arc::new(AtomicUsize::new(0));
13715    let counter_clone = counter.clone();
13716    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13717        let task_completion_item = closure_completion_item.clone();
13718        counter_clone.fetch_add(1, atomic::Ordering::Release);
13719        async move {
13720            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13721                is_incomplete: true,
13722                item_defaults: None,
13723                items: vec![task_completion_item],
13724            })))
13725        }
13726    });
13727
13728    cx.condition(|editor, _| editor.context_menu_visible())
13729        .await;
13730    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13731    assert!(request.next().await.is_some());
13732    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13733
13734    cx.simulate_keystrokes("S o m");
13735    cx.condition(|editor, _| editor.context_menu_visible())
13736        .await;
13737    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13738    assert!(request.next().await.is_some());
13739    assert!(request.next().await.is_some());
13740    assert!(request.next().await.is_some());
13741    request.close();
13742    assert!(request.next().await.is_none());
13743    assert_eq!(
13744        counter.load(atomic::Ordering::Acquire),
13745        4,
13746        "With the completions menu open, only one LSP request should happen per input"
13747    );
13748}
13749
13750#[gpui::test]
13751async fn test_toggle_comment(cx: &mut TestAppContext) {
13752    init_test(cx, |_| {});
13753    let mut cx = EditorTestContext::new(cx).await;
13754    let language = Arc::new(Language::new(
13755        LanguageConfig {
13756            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13757            ..Default::default()
13758        },
13759        Some(tree_sitter_rust::LANGUAGE.into()),
13760    ));
13761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13762
13763    // If multiple selections intersect a line, the line is only toggled once.
13764    cx.set_state(indoc! {"
13765        fn a() {
13766            «//b();
13767            ˇ»// «c();
13768            //ˇ»  d();
13769        }
13770    "});
13771
13772    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13773
13774    cx.assert_editor_state(indoc! {"
13775        fn a() {
13776            «b();
13777            c();
13778            ˇ» d();
13779        }
13780    "});
13781
13782    // The comment prefix is inserted at the same column for every line in a
13783    // selection.
13784    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13785
13786    cx.assert_editor_state(indoc! {"
13787        fn a() {
13788            // «b();
13789            // c();
13790            ˇ»//  d();
13791        }
13792    "});
13793
13794    // If a selection ends at the beginning of a line, that line is not toggled.
13795    cx.set_selections_state(indoc! {"
13796        fn a() {
13797            // b();
13798            «// c();
13799        ˇ»    //  d();
13800        }
13801    "});
13802
13803    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13804
13805    cx.assert_editor_state(indoc! {"
13806        fn a() {
13807            // b();
13808            «c();
13809        ˇ»    //  d();
13810        }
13811    "});
13812
13813    // If a selection span a single line and is empty, the line is toggled.
13814    cx.set_state(indoc! {"
13815        fn a() {
13816            a();
13817            b();
13818        ˇ
13819        }
13820    "});
13821
13822    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13823
13824    cx.assert_editor_state(indoc! {"
13825        fn a() {
13826            a();
13827            b();
13828        //•ˇ
13829        }
13830    "});
13831
13832    // If a selection span multiple lines, empty lines are not toggled.
13833    cx.set_state(indoc! {"
13834        fn a() {
13835            «a();
13836
13837            c();ˇ»
13838        }
13839    "});
13840
13841    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13842
13843    cx.assert_editor_state(indoc! {"
13844        fn a() {
13845            // «a();
13846
13847            // c();ˇ»
13848        }
13849    "});
13850
13851    // If a selection includes multiple comment prefixes, all lines are uncommented.
13852    cx.set_state(indoc! {"
13853        fn a() {
13854            «// a();
13855            /// b();
13856            //! c();ˇ»
13857        }
13858    "});
13859
13860    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13861
13862    cx.assert_editor_state(indoc! {"
13863        fn a() {
13864            «a();
13865            b();
13866            c();ˇ»
13867        }
13868    "});
13869}
13870
13871#[gpui::test]
13872async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13873    init_test(cx, |_| {});
13874    let mut cx = EditorTestContext::new(cx).await;
13875    let language = Arc::new(Language::new(
13876        LanguageConfig {
13877            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13878            ..Default::default()
13879        },
13880        Some(tree_sitter_rust::LANGUAGE.into()),
13881    ));
13882    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13883
13884    let toggle_comments = &ToggleComments {
13885        advance_downwards: false,
13886        ignore_indent: true,
13887    };
13888
13889    // If multiple selections intersect a line, the line is only toggled once.
13890    cx.set_state(indoc! {"
13891        fn a() {
13892        //    «b();
13893        //    c();
13894        //    ˇ» d();
13895        }
13896    "});
13897
13898    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13899
13900    cx.assert_editor_state(indoc! {"
13901        fn a() {
13902            «b();
13903            c();
13904            ˇ» d();
13905        }
13906    "});
13907
13908    // The comment prefix is inserted at the beginning of each line
13909    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13910
13911    cx.assert_editor_state(indoc! {"
13912        fn a() {
13913        //    «b();
13914        //    c();
13915        //    ˇ» d();
13916        }
13917    "});
13918
13919    // If a selection ends at the beginning of a line, that line is not toggled.
13920    cx.set_selections_state(indoc! {"
13921        fn a() {
13922        //    b();
13923        //    «c();
13924        ˇ»//     d();
13925        }
13926    "});
13927
13928    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13929
13930    cx.assert_editor_state(indoc! {"
13931        fn a() {
13932        //    b();
13933            «c();
13934        ˇ»//     d();
13935        }
13936    "});
13937
13938    // If a selection span a single line and is empty, the line is toggled.
13939    cx.set_state(indoc! {"
13940        fn a() {
13941            a();
13942            b();
13943        ˇ
13944        }
13945    "});
13946
13947    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13948
13949    cx.assert_editor_state(indoc! {"
13950        fn a() {
13951            a();
13952            b();
13953        //ˇ
13954        }
13955    "});
13956
13957    // If a selection span multiple lines, empty lines are not toggled.
13958    cx.set_state(indoc! {"
13959        fn a() {
13960            «a();
13961
13962            c();ˇ»
13963        }
13964    "});
13965
13966    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13967
13968    cx.assert_editor_state(indoc! {"
13969        fn a() {
13970        //    «a();
13971
13972        //    c();ˇ»
13973        }
13974    "});
13975
13976    // If a selection includes multiple comment prefixes, all lines are uncommented.
13977    cx.set_state(indoc! {"
13978        fn a() {
13979        //    «a();
13980        ///    b();
13981        //!    c();ˇ»
13982        }
13983    "});
13984
13985    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13986
13987    cx.assert_editor_state(indoc! {"
13988        fn a() {
13989            «a();
13990            b();
13991            c();ˇ»
13992        }
13993    "});
13994}
13995
13996#[gpui::test]
13997async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13998    init_test(cx, |_| {});
13999
14000    let language = Arc::new(Language::new(
14001        LanguageConfig {
14002            line_comments: vec!["// ".into()],
14003            ..Default::default()
14004        },
14005        Some(tree_sitter_rust::LANGUAGE.into()),
14006    ));
14007
14008    let mut cx = EditorTestContext::new(cx).await;
14009
14010    cx.language_registry().add(language.clone());
14011    cx.update_buffer(|buffer, cx| {
14012        buffer.set_language(Some(language), cx);
14013    });
14014
14015    let toggle_comments = &ToggleComments {
14016        advance_downwards: true,
14017        ignore_indent: false,
14018    };
14019
14020    // Single cursor on one line -> advance
14021    // Cursor moves horizontally 3 characters as well on non-blank line
14022    cx.set_state(indoc!(
14023        "fn a() {
14024             ˇdog();
14025             cat();
14026        }"
14027    ));
14028    cx.update_editor(|editor, window, cx| {
14029        editor.toggle_comments(toggle_comments, window, cx);
14030    });
14031    cx.assert_editor_state(indoc!(
14032        "fn a() {
14033             // dog();
14034             catˇ();
14035        }"
14036    ));
14037
14038    // Single selection on one line -> don't advance
14039    cx.set_state(indoc!(
14040        "fn a() {
14041             «dog()ˇ»;
14042             cat();
14043        }"
14044    ));
14045    cx.update_editor(|editor, window, cx| {
14046        editor.toggle_comments(toggle_comments, window, cx);
14047    });
14048    cx.assert_editor_state(indoc!(
14049        "fn a() {
14050             // «dog()ˇ»;
14051             cat();
14052        }"
14053    ));
14054
14055    // Multiple cursors on one line -> advance
14056    cx.set_state(indoc!(
14057        "fn a() {
14058             ˇdˇog();
14059             cat();
14060        }"
14061    ));
14062    cx.update_editor(|editor, window, cx| {
14063        editor.toggle_comments(toggle_comments, window, cx);
14064    });
14065    cx.assert_editor_state(indoc!(
14066        "fn a() {
14067             // dog();
14068             catˇ(ˇ);
14069        }"
14070    ));
14071
14072    // Multiple cursors on one line, with selection -> don't advance
14073    cx.set_state(indoc!(
14074        "fn a() {
14075             ˇdˇog«()ˇ»;
14076             cat();
14077        }"
14078    ));
14079    cx.update_editor(|editor, window, cx| {
14080        editor.toggle_comments(toggle_comments, window, cx);
14081    });
14082    cx.assert_editor_state(indoc!(
14083        "fn a() {
14084             // ˇdˇog«()ˇ»;
14085             cat();
14086        }"
14087    ));
14088
14089    // Single cursor on one line -> advance
14090    // Cursor moves to column 0 on blank line
14091    cx.set_state(indoc!(
14092        "fn a() {
14093             ˇdog();
14094
14095             cat();
14096        }"
14097    ));
14098    cx.update_editor(|editor, window, cx| {
14099        editor.toggle_comments(toggle_comments, window, cx);
14100    });
14101    cx.assert_editor_state(indoc!(
14102        "fn a() {
14103             // dog();
14104        ˇ
14105             cat();
14106        }"
14107    ));
14108
14109    // Single cursor on one line -> advance
14110    // Cursor starts and ends at column 0
14111    cx.set_state(indoc!(
14112        "fn a() {
14113         ˇ    dog();
14114             cat();
14115        }"
14116    ));
14117    cx.update_editor(|editor, window, cx| {
14118        editor.toggle_comments(toggle_comments, window, cx);
14119    });
14120    cx.assert_editor_state(indoc!(
14121        "fn a() {
14122             // dog();
14123         ˇ    cat();
14124        }"
14125    ));
14126}
14127
14128#[gpui::test]
14129async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14130    init_test(cx, |_| {});
14131
14132    let mut cx = EditorTestContext::new(cx).await;
14133
14134    let html_language = Arc::new(
14135        Language::new(
14136            LanguageConfig {
14137                name: "HTML".into(),
14138                block_comment: Some(BlockCommentConfig {
14139                    start: "<!-- ".into(),
14140                    prefix: "".into(),
14141                    end: " -->".into(),
14142                    tab_size: 0,
14143                }),
14144                ..Default::default()
14145            },
14146            Some(tree_sitter_html::LANGUAGE.into()),
14147        )
14148        .with_injection_query(
14149            r#"
14150            (script_element
14151                (raw_text) @injection.content
14152                (#set! injection.language "javascript"))
14153            "#,
14154        )
14155        .unwrap(),
14156    );
14157
14158    let javascript_language = Arc::new(Language::new(
14159        LanguageConfig {
14160            name: "JavaScript".into(),
14161            line_comments: vec!["// ".into()],
14162            ..Default::default()
14163        },
14164        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14165    ));
14166
14167    cx.language_registry().add(html_language.clone());
14168    cx.language_registry().add(javascript_language.clone());
14169    cx.update_buffer(|buffer, cx| {
14170        buffer.set_language(Some(html_language), cx);
14171    });
14172
14173    // Toggle comments for empty selections
14174    cx.set_state(
14175        &r#"
14176            <p>A</p>ˇ
14177            <p>B</p>ˇ
14178            <p>C</p>ˇ
14179        "#
14180        .unindent(),
14181    );
14182    cx.update_editor(|editor, window, cx| {
14183        editor.toggle_comments(&ToggleComments::default(), window, cx)
14184    });
14185    cx.assert_editor_state(
14186        &r#"
14187            <!-- <p>A</p>ˇ -->
14188            <!-- <p>B</p>ˇ -->
14189            <!-- <p>C</p>ˇ -->
14190        "#
14191        .unindent(),
14192    );
14193    cx.update_editor(|editor, window, cx| {
14194        editor.toggle_comments(&ToggleComments::default(), window, cx)
14195    });
14196    cx.assert_editor_state(
14197        &r#"
14198            <p>A</p>ˇ
14199            <p>B</p>ˇ
14200            <p>C</p>ˇ
14201        "#
14202        .unindent(),
14203    );
14204
14205    // Toggle comments for mixture of empty and non-empty selections, where
14206    // multiple selections occupy a given line.
14207    cx.set_state(
14208        &r#"
14209            <p>A«</p>
14210            <p>ˇ»B</p>ˇ
14211            <p>C«</p>
14212            <p>ˇ»D</p>ˇ
14213        "#
14214        .unindent(),
14215    );
14216
14217    cx.update_editor(|editor, window, cx| {
14218        editor.toggle_comments(&ToggleComments::default(), window, cx)
14219    });
14220    cx.assert_editor_state(
14221        &r#"
14222            <!-- <p>A«</p>
14223            <p>ˇ»B</p>ˇ -->
14224            <!-- <p>C«</p>
14225            <p>ˇ»D</p>ˇ -->
14226        "#
14227        .unindent(),
14228    );
14229    cx.update_editor(|editor, window, cx| {
14230        editor.toggle_comments(&ToggleComments::default(), window, cx)
14231    });
14232    cx.assert_editor_state(
14233        &r#"
14234            <p>A«</p>
14235            <p>ˇ»B</p>ˇ
14236            <p>C«</p>
14237            <p>ˇ»D</p>ˇ
14238        "#
14239        .unindent(),
14240    );
14241
14242    // Toggle comments when different languages are active for different
14243    // selections.
14244    cx.set_state(
14245        &r#"
14246            ˇ<script>
14247                ˇvar x = new Y();
14248            ˇ</script>
14249        "#
14250        .unindent(),
14251    );
14252    cx.executor().run_until_parked();
14253    cx.update_editor(|editor, window, cx| {
14254        editor.toggle_comments(&ToggleComments::default(), window, cx)
14255    });
14256    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14257    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14258    cx.assert_editor_state(
14259        &r#"
14260            <!-- ˇ<script> -->
14261                // ˇvar x = new Y();
14262            <!-- ˇ</script> -->
14263        "#
14264        .unindent(),
14265    );
14266}
14267
14268#[gpui::test]
14269fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14270    init_test(cx, |_| {});
14271
14272    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14273    let multibuffer = cx.new(|cx| {
14274        let mut multibuffer = MultiBuffer::new(ReadWrite);
14275        multibuffer.push_excerpts(
14276            buffer.clone(),
14277            [
14278                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14279                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14280            ],
14281            cx,
14282        );
14283        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14284        multibuffer
14285    });
14286
14287    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14288    editor.update_in(cx, |editor, window, cx| {
14289        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14290        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14291            s.select_ranges([
14292                Point::new(0, 0)..Point::new(0, 0),
14293                Point::new(1, 0)..Point::new(1, 0),
14294            ])
14295        });
14296
14297        editor.handle_input("X", window, cx);
14298        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14299        assert_eq!(
14300            editor.selections.ranges(cx),
14301            [
14302                Point::new(0, 1)..Point::new(0, 1),
14303                Point::new(1, 1)..Point::new(1, 1),
14304            ]
14305        );
14306
14307        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14309            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14310        });
14311        editor.backspace(&Default::default(), window, cx);
14312        assert_eq!(editor.text(cx), "Xa\nbbb");
14313        assert_eq!(
14314            editor.selections.ranges(cx),
14315            [Point::new(1, 0)..Point::new(1, 0)]
14316        );
14317
14318        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14319            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14320        });
14321        editor.backspace(&Default::default(), window, cx);
14322        assert_eq!(editor.text(cx), "X\nbb");
14323        assert_eq!(
14324            editor.selections.ranges(cx),
14325            [Point::new(0, 1)..Point::new(0, 1)]
14326        );
14327    });
14328}
14329
14330#[gpui::test]
14331fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14332    init_test(cx, |_| {});
14333
14334    let markers = vec![('[', ']').into(), ('(', ')').into()];
14335    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14336        indoc! {"
14337            [aaaa
14338            (bbbb]
14339            cccc)",
14340        },
14341        markers.clone(),
14342    );
14343    let excerpt_ranges = markers.into_iter().map(|marker| {
14344        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14345        ExcerptRange::new(context.clone())
14346    });
14347    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14348    let multibuffer = cx.new(|cx| {
14349        let mut multibuffer = MultiBuffer::new(ReadWrite);
14350        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14351        multibuffer
14352    });
14353
14354    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14355    editor.update_in(cx, |editor, window, cx| {
14356        let (expected_text, selection_ranges) = marked_text_ranges(
14357            indoc! {"
14358                aaaa
14359                bˇbbb
14360                bˇbbˇb
14361                cccc"
14362            },
14363            true,
14364        );
14365        assert_eq!(editor.text(cx), expected_text);
14366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14367            s.select_ranges(selection_ranges)
14368        });
14369
14370        editor.handle_input("X", window, cx);
14371
14372        let (expected_text, expected_selections) = marked_text_ranges(
14373            indoc! {"
14374                aaaa
14375                bXˇbbXb
14376                bXˇbbXˇb
14377                cccc"
14378            },
14379            false,
14380        );
14381        assert_eq!(editor.text(cx), expected_text);
14382        assert_eq!(editor.selections.ranges(cx), expected_selections);
14383
14384        editor.newline(&Newline, window, cx);
14385        let (expected_text, expected_selections) = marked_text_ranges(
14386            indoc! {"
14387                aaaa
14388                bX
14389                ˇbbX
14390                b
14391                bX
14392                ˇbbX
14393                ˇb
14394                cccc"
14395            },
14396            false,
14397        );
14398        assert_eq!(editor.text(cx), expected_text);
14399        assert_eq!(editor.selections.ranges(cx), expected_selections);
14400    });
14401}
14402
14403#[gpui::test]
14404fn test_refresh_selections(cx: &mut TestAppContext) {
14405    init_test(cx, |_| {});
14406
14407    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14408    let mut excerpt1_id = None;
14409    let multibuffer = cx.new(|cx| {
14410        let mut multibuffer = MultiBuffer::new(ReadWrite);
14411        excerpt1_id = multibuffer
14412            .push_excerpts(
14413                buffer.clone(),
14414                [
14415                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14416                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14417                ],
14418                cx,
14419            )
14420            .into_iter()
14421            .next();
14422        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14423        multibuffer
14424    });
14425
14426    let editor = cx.add_window(|window, cx| {
14427        let mut editor = build_editor(multibuffer.clone(), window, cx);
14428        let snapshot = editor.snapshot(window, cx);
14429        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14430            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14431        });
14432        editor.begin_selection(
14433            Point::new(2, 1).to_display_point(&snapshot),
14434            true,
14435            1,
14436            window,
14437            cx,
14438        );
14439        assert_eq!(
14440            editor.selections.ranges(cx),
14441            [
14442                Point::new(1, 3)..Point::new(1, 3),
14443                Point::new(2, 1)..Point::new(2, 1),
14444            ]
14445        );
14446        editor
14447    });
14448
14449    // Refreshing selections is a no-op when excerpts haven't changed.
14450    _ = editor.update(cx, |editor, window, cx| {
14451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14452        assert_eq!(
14453            editor.selections.ranges(cx),
14454            [
14455                Point::new(1, 3)..Point::new(1, 3),
14456                Point::new(2, 1)..Point::new(2, 1),
14457            ]
14458        );
14459    });
14460
14461    multibuffer.update(cx, |multibuffer, cx| {
14462        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14463    });
14464    _ = editor.update(cx, |editor, window, cx| {
14465        // Removing an excerpt causes the first selection to become degenerate.
14466        assert_eq!(
14467            editor.selections.ranges(cx),
14468            [
14469                Point::new(0, 0)..Point::new(0, 0),
14470                Point::new(0, 1)..Point::new(0, 1)
14471            ]
14472        );
14473
14474        // Refreshing selections will relocate the first selection to the original buffer
14475        // location.
14476        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14477        assert_eq!(
14478            editor.selections.ranges(cx),
14479            [
14480                Point::new(0, 1)..Point::new(0, 1),
14481                Point::new(0, 3)..Point::new(0, 3)
14482            ]
14483        );
14484        assert!(editor.selections.pending_anchor().is_some());
14485    });
14486}
14487
14488#[gpui::test]
14489fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14490    init_test(cx, |_| {});
14491
14492    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14493    let mut excerpt1_id = None;
14494    let multibuffer = cx.new(|cx| {
14495        let mut multibuffer = MultiBuffer::new(ReadWrite);
14496        excerpt1_id = multibuffer
14497            .push_excerpts(
14498                buffer.clone(),
14499                [
14500                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14501                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14502                ],
14503                cx,
14504            )
14505            .into_iter()
14506            .next();
14507        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14508        multibuffer
14509    });
14510
14511    let editor = cx.add_window(|window, cx| {
14512        let mut editor = build_editor(multibuffer.clone(), window, cx);
14513        let snapshot = editor.snapshot(window, cx);
14514        editor.begin_selection(
14515            Point::new(1, 3).to_display_point(&snapshot),
14516            false,
14517            1,
14518            window,
14519            cx,
14520        );
14521        assert_eq!(
14522            editor.selections.ranges(cx),
14523            [Point::new(1, 3)..Point::new(1, 3)]
14524        );
14525        editor
14526    });
14527
14528    multibuffer.update(cx, |multibuffer, cx| {
14529        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14530    });
14531    _ = editor.update(cx, |editor, window, cx| {
14532        assert_eq!(
14533            editor.selections.ranges(cx),
14534            [Point::new(0, 0)..Point::new(0, 0)]
14535        );
14536
14537        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14538        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14539        assert_eq!(
14540            editor.selections.ranges(cx),
14541            [Point::new(0, 3)..Point::new(0, 3)]
14542        );
14543        assert!(editor.selections.pending_anchor().is_some());
14544    });
14545}
14546
14547#[gpui::test]
14548async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14549    init_test(cx, |_| {});
14550
14551    let language = Arc::new(
14552        Language::new(
14553            LanguageConfig {
14554                brackets: BracketPairConfig {
14555                    pairs: vec![
14556                        BracketPair {
14557                            start: "{".to_string(),
14558                            end: "}".to_string(),
14559                            close: true,
14560                            surround: true,
14561                            newline: true,
14562                        },
14563                        BracketPair {
14564                            start: "/* ".to_string(),
14565                            end: " */".to_string(),
14566                            close: true,
14567                            surround: true,
14568                            newline: true,
14569                        },
14570                    ],
14571                    ..Default::default()
14572                },
14573                ..Default::default()
14574            },
14575            Some(tree_sitter_rust::LANGUAGE.into()),
14576        )
14577        .with_indents_query("")
14578        .unwrap(),
14579    );
14580
14581    let text = concat!(
14582        "{   }\n",     //
14583        "  x\n",       //
14584        "  /*   */\n", //
14585        "x\n",         //
14586        "{{} }\n",     //
14587    );
14588
14589    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14590    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14591    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14592    editor
14593        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14594        .await;
14595
14596    editor.update_in(cx, |editor, window, cx| {
14597        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14598            s.select_display_ranges([
14599                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14600                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14601                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14602            ])
14603        });
14604        editor.newline(&Newline, window, cx);
14605
14606        assert_eq!(
14607            editor.buffer().read(cx).read(cx).text(),
14608            concat!(
14609                "{ \n",    // Suppress rustfmt
14610                "\n",      //
14611                "}\n",     //
14612                "  x\n",   //
14613                "  /* \n", //
14614                "  \n",    //
14615                "  */\n",  //
14616                "x\n",     //
14617                "{{} \n",  //
14618                "}\n",     //
14619            )
14620        );
14621    });
14622}
14623
14624#[gpui::test]
14625fn test_highlighted_ranges(cx: &mut TestAppContext) {
14626    init_test(cx, |_| {});
14627
14628    let editor = cx.add_window(|window, cx| {
14629        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14630        build_editor(buffer.clone(), window, cx)
14631    });
14632
14633    _ = editor.update(cx, |editor, window, cx| {
14634        struct Type1;
14635        struct Type2;
14636
14637        let buffer = editor.buffer.read(cx).snapshot(cx);
14638
14639        let anchor_range =
14640            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14641
14642        editor.highlight_background::<Type1>(
14643            &[
14644                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14645                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14646                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14647                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14648            ],
14649            |_| Hsla::red(),
14650            cx,
14651        );
14652        editor.highlight_background::<Type2>(
14653            &[
14654                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14655                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14656                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14657                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14658            ],
14659            |_| Hsla::green(),
14660            cx,
14661        );
14662
14663        let snapshot = editor.snapshot(window, cx);
14664        let mut highlighted_ranges = editor.background_highlights_in_range(
14665            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14666            &snapshot,
14667            cx.theme(),
14668        );
14669        // Enforce a consistent ordering based on color without relying on the ordering of the
14670        // highlight's `TypeId` which is non-executor.
14671        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14672        assert_eq!(
14673            highlighted_ranges,
14674            &[
14675                (
14676                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14677                    Hsla::red(),
14678                ),
14679                (
14680                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14681                    Hsla::red(),
14682                ),
14683                (
14684                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14685                    Hsla::green(),
14686                ),
14687                (
14688                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14689                    Hsla::green(),
14690                ),
14691            ]
14692        );
14693        assert_eq!(
14694            editor.background_highlights_in_range(
14695                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14696                &snapshot,
14697                cx.theme(),
14698            ),
14699            &[(
14700                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14701                Hsla::red(),
14702            )]
14703        );
14704    });
14705}
14706
14707#[gpui::test]
14708async fn test_following(cx: &mut TestAppContext) {
14709    init_test(cx, |_| {});
14710
14711    let fs = FakeFs::new(cx.executor());
14712    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14713
14714    let buffer = project.update(cx, |project, cx| {
14715        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14716        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14717    });
14718    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14719    let follower = cx.update(|cx| {
14720        cx.open_window(
14721            WindowOptions {
14722                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14723                    gpui::Point::new(px(0.), px(0.)),
14724                    gpui::Point::new(px(10.), px(80.)),
14725                ))),
14726                ..Default::default()
14727            },
14728            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14729        )
14730        .unwrap()
14731    });
14732
14733    let is_still_following = Rc::new(RefCell::new(true));
14734    let follower_edit_event_count = Rc::new(RefCell::new(0));
14735    let pending_update = Rc::new(RefCell::new(None));
14736    let leader_entity = leader.root(cx).unwrap();
14737    let follower_entity = follower.root(cx).unwrap();
14738    _ = follower.update(cx, {
14739        let update = pending_update.clone();
14740        let is_still_following = is_still_following.clone();
14741        let follower_edit_event_count = follower_edit_event_count.clone();
14742        |_, window, cx| {
14743            cx.subscribe_in(
14744                &leader_entity,
14745                window,
14746                move |_, leader, event, window, cx| {
14747                    leader.read(cx).add_event_to_update_proto(
14748                        event,
14749                        &mut update.borrow_mut(),
14750                        window,
14751                        cx,
14752                    );
14753                },
14754            )
14755            .detach();
14756
14757            cx.subscribe_in(
14758                &follower_entity,
14759                window,
14760                move |_, _, event: &EditorEvent, _window, _cx| {
14761                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14762                        *is_still_following.borrow_mut() = false;
14763                    }
14764
14765                    if let EditorEvent::BufferEdited = event {
14766                        *follower_edit_event_count.borrow_mut() += 1;
14767                    }
14768                },
14769            )
14770            .detach();
14771        }
14772    });
14773
14774    // Update the selections only
14775    _ = leader.update(cx, |leader, window, cx| {
14776        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14777            s.select_ranges([1..1])
14778        });
14779    });
14780    follower
14781        .update(cx, |follower, window, cx| {
14782            follower.apply_update_proto(
14783                &project,
14784                pending_update.borrow_mut().take().unwrap(),
14785                window,
14786                cx,
14787            )
14788        })
14789        .unwrap()
14790        .await
14791        .unwrap();
14792    _ = follower.update(cx, |follower, _, cx| {
14793        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14794    });
14795    assert!(*is_still_following.borrow());
14796    assert_eq!(*follower_edit_event_count.borrow(), 0);
14797
14798    // Update the scroll position only
14799    _ = leader.update(cx, |leader, window, cx| {
14800        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14801    });
14802    follower
14803        .update(cx, |follower, window, cx| {
14804            follower.apply_update_proto(
14805                &project,
14806                pending_update.borrow_mut().take().unwrap(),
14807                window,
14808                cx,
14809            )
14810        })
14811        .unwrap()
14812        .await
14813        .unwrap();
14814    assert_eq!(
14815        follower
14816            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14817            .unwrap(),
14818        gpui::Point::new(1.5, 3.5)
14819    );
14820    assert!(*is_still_following.borrow());
14821    assert_eq!(*follower_edit_event_count.borrow(), 0);
14822
14823    // Update the selections and scroll position. The follower's scroll position is updated
14824    // via autoscroll, not via the leader's exact scroll position.
14825    _ = leader.update(cx, |leader, window, cx| {
14826        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14827            s.select_ranges([0..0])
14828        });
14829        leader.request_autoscroll(Autoscroll::newest(), cx);
14830        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14831    });
14832    follower
14833        .update(cx, |follower, window, cx| {
14834            follower.apply_update_proto(
14835                &project,
14836                pending_update.borrow_mut().take().unwrap(),
14837                window,
14838                cx,
14839            )
14840        })
14841        .unwrap()
14842        .await
14843        .unwrap();
14844    _ = follower.update(cx, |follower, _, cx| {
14845        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14846        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14847    });
14848    assert!(*is_still_following.borrow());
14849
14850    // Creating a pending selection that precedes another selection
14851    _ = leader.update(cx, |leader, window, cx| {
14852        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14853            s.select_ranges([1..1])
14854        });
14855        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14856    });
14857    follower
14858        .update(cx, |follower, window, cx| {
14859            follower.apply_update_proto(
14860                &project,
14861                pending_update.borrow_mut().take().unwrap(),
14862                window,
14863                cx,
14864            )
14865        })
14866        .unwrap()
14867        .await
14868        .unwrap();
14869    _ = follower.update(cx, |follower, _, cx| {
14870        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14871    });
14872    assert!(*is_still_following.borrow());
14873
14874    // Extend the pending selection so that it surrounds another selection
14875    _ = leader.update(cx, |leader, window, cx| {
14876        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14877    });
14878    follower
14879        .update(cx, |follower, window, cx| {
14880            follower.apply_update_proto(
14881                &project,
14882                pending_update.borrow_mut().take().unwrap(),
14883                window,
14884                cx,
14885            )
14886        })
14887        .unwrap()
14888        .await
14889        .unwrap();
14890    _ = follower.update(cx, |follower, _, cx| {
14891        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14892    });
14893
14894    // Scrolling locally breaks the follow
14895    _ = follower.update(cx, |follower, window, cx| {
14896        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14897        follower.set_scroll_anchor(
14898            ScrollAnchor {
14899                anchor: top_anchor,
14900                offset: gpui::Point::new(0.0, 0.5),
14901            },
14902            window,
14903            cx,
14904        );
14905    });
14906    assert!(!(*is_still_following.borrow()));
14907}
14908
14909#[gpui::test]
14910async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14911    init_test(cx, |_| {});
14912
14913    let fs = FakeFs::new(cx.executor());
14914    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14915    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14916    let pane = workspace
14917        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14918        .unwrap();
14919
14920    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14921
14922    let leader = pane.update_in(cx, |_, window, cx| {
14923        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14924        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14925    });
14926
14927    // Start following the editor when it has no excerpts.
14928    let mut state_message =
14929        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14930    let workspace_entity = workspace.root(cx).unwrap();
14931    let follower_1 = cx
14932        .update_window(*workspace.deref(), |_, window, cx| {
14933            Editor::from_state_proto(
14934                workspace_entity,
14935                ViewId {
14936                    creator: CollaboratorId::PeerId(PeerId::default()),
14937                    id: 0,
14938                },
14939                &mut state_message,
14940                window,
14941                cx,
14942            )
14943        })
14944        .unwrap()
14945        .unwrap()
14946        .await
14947        .unwrap();
14948
14949    let update_message = Rc::new(RefCell::new(None));
14950    follower_1.update_in(cx, {
14951        let update = update_message.clone();
14952        |_, window, cx| {
14953            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14954                leader.read(cx).add_event_to_update_proto(
14955                    event,
14956                    &mut update.borrow_mut(),
14957                    window,
14958                    cx,
14959                );
14960            })
14961            .detach();
14962        }
14963    });
14964
14965    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14966        (
14967            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14968            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14969        )
14970    });
14971
14972    // Insert some excerpts.
14973    leader.update(cx, |leader, cx| {
14974        leader.buffer.update(cx, |multibuffer, cx| {
14975            multibuffer.set_excerpts_for_path(
14976                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14977                buffer_1.clone(),
14978                vec![
14979                    Point::row_range(0..3),
14980                    Point::row_range(1..6),
14981                    Point::row_range(12..15),
14982                ],
14983                0,
14984                cx,
14985            );
14986            multibuffer.set_excerpts_for_path(
14987                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14988                buffer_2.clone(),
14989                vec![Point::row_range(0..6), Point::row_range(8..12)],
14990                0,
14991                cx,
14992            );
14993        });
14994    });
14995
14996    // Apply the update of adding the excerpts.
14997    follower_1
14998        .update_in(cx, |follower, window, cx| {
14999            follower.apply_update_proto(
15000                &project,
15001                update_message.borrow().clone().unwrap(),
15002                window,
15003                cx,
15004            )
15005        })
15006        .await
15007        .unwrap();
15008    assert_eq!(
15009        follower_1.update(cx, |editor, cx| editor.text(cx)),
15010        leader.update(cx, |editor, cx| editor.text(cx))
15011    );
15012    update_message.borrow_mut().take();
15013
15014    // Start following separately after it already has excerpts.
15015    let mut state_message =
15016        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15017    let workspace_entity = workspace.root(cx).unwrap();
15018    let follower_2 = cx
15019        .update_window(*workspace.deref(), |_, window, cx| {
15020            Editor::from_state_proto(
15021                workspace_entity,
15022                ViewId {
15023                    creator: CollaboratorId::PeerId(PeerId::default()),
15024                    id: 0,
15025                },
15026                &mut state_message,
15027                window,
15028                cx,
15029            )
15030        })
15031        .unwrap()
15032        .unwrap()
15033        .await
15034        .unwrap();
15035    assert_eq!(
15036        follower_2.update(cx, |editor, cx| editor.text(cx)),
15037        leader.update(cx, |editor, cx| editor.text(cx))
15038    );
15039
15040    // Remove some excerpts.
15041    leader.update(cx, |leader, cx| {
15042        leader.buffer.update(cx, |multibuffer, cx| {
15043            let excerpt_ids = multibuffer.excerpt_ids();
15044            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15045            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15046        });
15047    });
15048
15049    // Apply the update of removing the excerpts.
15050    follower_1
15051        .update_in(cx, |follower, window, cx| {
15052            follower.apply_update_proto(
15053                &project,
15054                update_message.borrow().clone().unwrap(),
15055                window,
15056                cx,
15057            )
15058        })
15059        .await
15060        .unwrap();
15061    follower_2
15062        .update_in(cx, |follower, window, cx| {
15063            follower.apply_update_proto(
15064                &project,
15065                update_message.borrow().clone().unwrap(),
15066                window,
15067                cx,
15068            )
15069        })
15070        .await
15071        .unwrap();
15072    update_message.borrow_mut().take();
15073    assert_eq!(
15074        follower_1.update(cx, |editor, cx| editor.text(cx)),
15075        leader.update(cx, |editor, cx| editor.text(cx))
15076    );
15077}
15078
15079#[gpui::test]
15080async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15081    init_test(cx, |_| {});
15082
15083    let mut cx = EditorTestContext::new(cx).await;
15084    let lsp_store =
15085        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15086
15087    cx.set_state(indoc! {"
15088        ˇfn func(abc def: i32) -> u32 {
15089        }
15090    "});
15091
15092    cx.update(|_, cx| {
15093        lsp_store.update(cx, |lsp_store, cx| {
15094            lsp_store
15095                .update_diagnostics(
15096                    LanguageServerId(0),
15097                    lsp::PublishDiagnosticsParams {
15098                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15099                        version: None,
15100                        diagnostics: vec![
15101                            lsp::Diagnostic {
15102                                range: lsp::Range::new(
15103                                    lsp::Position::new(0, 11),
15104                                    lsp::Position::new(0, 12),
15105                                ),
15106                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15107                                ..Default::default()
15108                            },
15109                            lsp::Diagnostic {
15110                                range: lsp::Range::new(
15111                                    lsp::Position::new(0, 12),
15112                                    lsp::Position::new(0, 15),
15113                                ),
15114                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15115                                ..Default::default()
15116                            },
15117                            lsp::Diagnostic {
15118                                range: lsp::Range::new(
15119                                    lsp::Position::new(0, 25),
15120                                    lsp::Position::new(0, 28),
15121                                ),
15122                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15123                                ..Default::default()
15124                            },
15125                        ],
15126                    },
15127                    None,
15128                    DiagnosticSourceKind::Pushed,
15129                    &[],
15130                    cx,
15131                )
15132                .unwrap()
15133        });
15134    });
15135
15136    executor.run_until_parked();
15137
15138    cx.update_editor(|editor, window, cx| {
15139        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15140    });
15141
15142    cx.assert_editor_state(indoc! {"
15143        fn func(abc def: i32) -> ˇu32 {
15144        }
15145    "});
15146
15147    cx.update_editor(|editor, window, cx| {
15148        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15149    });
15150
15151    cx.assert_editor_state(indoc! {"
15152        fn func(abc ˇdef: i32) -> u32 {
15153        }
15154    "});
15155
15156    cx.update_editor(|editor, window, cx| {
15157        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15158    });
15159
15160    cx.assert_editor_state(indoc! {"
15161        fn func(abcˇ def: i32) -> u32 {
15162        }
15163    "});
15164
15165    cx.update_editor(|editor, window, cx| {
15166        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15167    });
15168
15169    cx.assert_editor_state(indoc! {"
15170        fn func(abc def: i32) -> ˇu32 {
15171        }
15172    "});
15173}
15174
15175#[gpui::test]
15176async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15177    init_test(cx, |_| {});
15178
15179    let mut cx = EditorTestContext::new(cx).await;
15180
15181    let diff_base = r#"
15182        use some::mod;
15183
15184        const A: u32 = 42;
15185
15186        fn main() {
15187            println!("hello");
15188
15189            println!("world");
15190        }
15191        "#
15192    .unindent();
15193
15194    // Edits are modified, removed, modified, added
15195    cx.set_state(
15196        &r#"
15197        use some::modified;
15198
15199        ˇ
15200        fn main() {
15201            println!("hello there");
15202
15203            println!("around the");
15204            println!("world");
15205        }
15206        "#
15207        .unindent(),
15208    );
15209
15210    cx.set_head_text(&diff_base);
15211    executor.run_until_parked();
15212
15213    cx.update_editor(|editor, window, cx| {
15214        //Wrap around the bottom of the buffer
15215        for _ in 0..3 {
15216            editor.go_to_next_hunk(&GoToHunk, window, cx);
15217        }
15218    });
15219
15220    cx.assert_editor_state(
15221        &r#"
15222        ˇuse some::modified;
15223
15224
15225        fn main() {
15226            println!("hello there");
15227
15228            println!("around the");
15229            println!("world");
15230        }
15231        "#
15232        .unindent(),
15233    );
15234
15235    cx.update_editor(|editor, window, cx| {
15236        //Wrap around the top of the buffer
15237        for _ in 0..2 {
15238            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15239        }
15240    });
15241
15242    cx.assert_editor_state(
15243        &r#"
15244        use some::modified;
15245
15246
15247        fn main() {
15248        ˇ    println!("hello there");
15249
15250            println!("around the");
15251            println!("world");
15252        }
15253        "#
15254        .unindent(),
15255    );
15256
15257    cx.update_editor(|editor, window, cx| {
15258        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15259    });
15260
15261    cx.assert_editor_state(
15262        &r#"
15263        use some::modified;
15264
15265        ˇ
15266        fn main() {
15267            println!("hello there");
15268
15269            println!("around the");
15270            println!("world");
15271        }
15272        "#
15273        .unindent(),
15274    );
15275
15276    cx.update_editor(|editor, window, cx| {
15277        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15278    });
15279
15280    cx.assert_editor_state(
15281        &r#"
15282        ˇuse some::modified;
15283
15284
15285        fn main() {
15286            println!("hello there");
15287
15288            println!("around the");
15289            println!("world");
15290        }
15291        "#
15292        .unindent(),
15293    );
15294
15295    cx.update_editor(|editor, window, cx| {
15296        for _ in 0..2 {
15297            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15298        }
15299    });
15300
15301    cx.assert_editor_state(
15302        &r#"
15303        use some::modified;
15304
15305
15306        fn main() {
15307        ˇ    println!("hello there");
15308
15309            println!("around the");
15310            println!("world");
15311        }
15312        "#
15313        .unindent(),
15314    );
15315
15316    cx.update_editor(|editor, window, cx| {
15317        editor.fold(&Fold, window, cx);
15318    });
15319
15320    cx.update_editor(|editor, window, cx| {
15321        editor.go_to_next_hunk(&GoToHunk, window, cx);
15322    });
15323
15324    cx.assert_editor_state(
15325        &r#"
15326        ˇuse some::modified;
15327
15328
15329        fn main() {
15330            println!("hello there");
15331
15332            println!("around the");
15333            println!("world");
15334        }
15335        "#
15336        .unindent(),
15337    );
15338}
15339
15340#[test]
15341fn test_split_words() {
15342    fn split(text: &str) -> Vec<&str> {
15343        split_words(text).collect()
15344    }
15345
15346    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15347    assert_eq!(split("hello_world"), &["hello_", "world"]);
15348    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15349    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15350    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15351    assert_eq!(split("helloworld"), &["helloworld"]);
15352
15353    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15354}
15355
15356#[gpui::test]
15357async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15358    init_test(cx, |_| {});
15359
15360    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15361    let mut assert = |before, after| {
15362        let _state_context = cx.set_state(before);
15363        cx.run_until_parked();
15364        cx.update_editor(|editor, window, cx| {
15365            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15366        });
15367        cx.run_until_parked();
15368        cx.assert_editor_state(after);
15369    };
15370
15371    // Outside bracket jumps to outside of matching bracket
15372    assert("console.logˇ(var);", "console.log(var)ˇ;");
15373    assert("console.log(var)ˇ;", "console.logˇ(var);");
15374
15375    // Inside bracket jumps to inside of matching bracket
15376    assert("console.log(ˇvar);", "console.log(varˇ);");
15377    assert("console.log(varˇ);", "console.log(ˇvar);");
15378
15379    // When outside a bracket and inside, favor jumping to the inside bracket
15380    assert(
15381        "console.log('foo', [1, 2, 3]ˇ);",
15382        "console.log(ˇ'foo', [1, 2, 3]);",
15383    );
15384    assert(
15385        "console.log(ˇ'foo', [1, 2, 3]);",
15386        "console.log('foo', [1, 2, 3]ˇ);",
15387    );
15388
15389    // Bias forward if two options are equally likely
15390    assert(
15391        "let result = curried_fun()ˇ();",
15392        "let result = curried_fun()()ˇ;",
15393    );
15394
15395    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15396    assert(
15397        indoc! {"
15398            function test() {
15399                console.log('test')ˇ
15400            }"},
15401        indoc! {"
15402            function test() {
15403                console.logˇ('test')
15404            }"},
15405    );
15406}
15407
15408#[gpui::test]
15409async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15410    init_test(cx, |_| {});
15411
15412    let fs = FakeFs::new(cx.executor());
15413    fs.insert_tree(
15414        path!("/a"),
15415        json!({
15416            "main.rs": "fn main() { let a = 5; }",
15417            "other.rs": "// Test file",
15418        }),
15419    )
15420    .await;
15421    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15422
15423    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15424    language_registry.add(Arc::new(Language::new(
15425        LanguageConfig {
15426            name: "Rust".into(),
15427            matcher: LanguageMatcher {
15428                path_suffixes: vec!["rs".to_string()],
15429                ..Default::default()
15430            },
15431            brackets: BracketPairConfig {
15432                pairs: vec![BracketPair {
15433                    start: "{".to_string(),
15434                    end: "}".to_string(),
15435                    close: true,
15436                    surround: true,
15437                    newline: true,
15438                }],
15439                disabled_scopes_by_bracket_ix: Vec::new(),
15440            },
15441            ..Default::default()
15442        },
15443        Some(tree_sitter_rust::LANGUAGE.into()),
15444    )));
15445    let mut fake_servers = language_registry.register_fake_lsp(
15446        "Rust",
15447        FakeLspAdapter {
15448            capabilities: lsp::ServerCapabilities {
15449                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15450                    first_trigger_character: "{".to_string(),
15451                    more_trigger_character: None,
15452                }),
15453                ..Default::default()
15454            },
15455            ..Default::default()
15456        },
15457    );
15458
15459    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15460
15461    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15462
15463    let worktree_id = workspace
15464        .update(cx, |workspace, _, cx| {
15465            workspace.project().update(cx, |project, cx| {
15466                project.worktrees(cx).next().unwrap().read(cx).id()
15467            })
15468        })
15469        .unwrap();
15470
15471    let buffer = project
15472        .update(cx, |project, cx| {
15473            project.open_local_buffer(path!("/a/main.rs"), cx)
15474        })
15475        .await
15476        .unwrap();
15477    let editor_handle = workspace
15478        .update(cx, |workspace, window, cx| {
15479            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15480        })
15481        .unwrap()
15482        .await
15483        .unwrap()
15484        .downcast::<Editor>()
15485        .unwrap();
15486
15487    cx.executor().start_waiting();
15488    let fake_server = fake_servers.next().await.unwrap();
15489
15490    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15491        |params, _| async move {
15492            assert_eq!(
15493                params.text_document_position.text_document.uri,
15494                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15495            );
15496            assert_eq!(
15497                params.text_document_position.position,
15498                lsp::Position::new(0, 21),
15499            );
15500
15501            Ok(Some(vec![lsp::TextEdit {
15502                new_text: "]".to_string(),
15503                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15504            }]))
15505        },
15506    );
15507
15508    editor_handle.update_in(cx, |editor, window, cx| {
15509        window.focus(&editor.focus_handle(cx));
15510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15511            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15512        });
15513        editor.handle_input("{", window, cx);
15514    });
15515
15516    cx.executor().run_until_parked();
15517
15518    buffer.update(cx, |buffer, _| {
15519        assert_eq!(
15520            buffer.text(),
15521            "fn main() { let a = {5}; }",
15522            "No extra braces from on type formatting should appear in the buffer"
15523        )
15524    });
15525}
15526
15527#[gpui::test(iterations = 20, seeds(31))]
15528async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15529    init_test(cx, |_| {});
15530
15531    let mut cx = EditorLspTestContext::new_rust(
15532        lsp::ServerCapabilities {
15533            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15534                first_trigger_character: ".".to_string(),
15535                more_trigger_character: None,
15536            }),
15537            ..Default::default()
15538        },
15539        cx,
15540    )
15541    .await;
15542
15543    cx.update_buffer(|buffer, _| {
15544        // This causes autoindent to be async.
15545        buffer.set_sync_parse_timeout(Duration::ZERO)
15546    });
15547
15548    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15549    cx.simulate_keystroke("\n");
15550    cx.run_until_parked();
15551
15552    let buffer_cloned =
15553        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15554    let mut request =
15555        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15556            let buffer_cloned = buffer_cloned.clone();
15557            async move {
15558                buffer_cloned.update(&mut cx, |buffer, _| {
15559                    assert_eq!(
15560                        buffer.text(),
15561                        "fn c() {\n    d()\n        .\n}\n",
15562                        "OnTypeFormatting should triggered after autoindent applied"
15563                    )
15564                })?;
15565
15566                Ok(Some(vec![]))
15567            }
15568        });
15569
15570    cx.simulate_keystroke(".");
15571    cx.run_until_parked();
15572
15573    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15574    assert!(request.next().await.is_some());
15575    request.close();
15576    assert!(request.next().await.is_none());
15577}
15578
15579#[gpui::test]
15580async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15581    init_test(cx, |_| {});
15582
15583    let fs = FakeFs::new(cx.executor());
15584    fs.insert_tree(
15585        path!("/a"),
15586        json!({
15587            "main.rs": "fn main() { let a = 5; }",
15588            "other.rs": "// Test file",
15589        }),
15590    )
15591    .await;
15592
15593    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15594
15595    let server_restarts = Arc::new(AtomicUsize::new(0));
15596    let closure_restarts = Arc::clone(&server_restarts);
15597    let language_server_name = "test language server";
15598    let language_name: LanguageName = "Rust".into();
15599
15600    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15601    language_registry.add(Arc::new(Language::new(
15602        LanguageConfig {
15603            name: language_name.clone(),
15604            matcher: LanguageMatcher {
15605                path_suffixes: vec!["rs".to_string()],
15606                ..Default::default()
15607            },
15608            ..Default::default()
15609        },
15610        Some(tree_sitter_rust::LANGUAGE.into()),
15611    )));
15612    let mut fake_servers = language_registry.register_fake_lsp(
15613        "Rust",
15614        FakeLspAdapter {
15615            name: language_server_name,
15616            initialization_options: Some(json!({
15617                "testOptionValue": true
15618            })),
15619            initializer: Some(Box::new(move |fake_server| {
15620                let task_restarts = Arc::clone(&closure_restarts);
15621                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15622                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15623                    futures::future::ready(Ok(()))
15624                });
15625            })),
15626            ..Default::default()
15627        },
15628    );
15629
15630    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15631    let _buffer = project
15632        .update(cx, |project, cx| {
15633            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15634        })
15635        .await
15636        .unwrap();
15637    let _fake_server = fake_servers.next().await.unwrap();
15638    update_test_language_settings(cx, |language_settings| {
15639        language_settings.languages.0.insert(
15640            language_name.clone(),
15641            LanguageSettingsContent {
15642                tab_size: NonZeroU32::new(8),
15643                ..Default::default()
15644            },
15645        );
15646    });
15647    cx.executor().run_until_parked();
15648    assert_eq!(
15649        server_restarts.load(atomic::Ordering::Acquire),
15650        0,
15651        "Should not restart LSP server on an unrelated change"
15652    );
15653
15654    update_test_project_settings(cx, |project_settings| {
15655        project_settings.lsp.insert(
15656            "Some other server name".into(),
15657            LspSettings {
15658                binary: None,
15659                settings: None,
15660                initialization_options: Some(json!({
15661                    "some other init value": false
15662                })),
15663                enable_lsp_tasks: false,
15664            },
15665        );
15666    });
15667    cx.executor().run_until_parked();
15668    assert_eq!(
15669        server_restarts.load(atomic::Ordering::Acquire),
15670        0,
15671        "Should not restart LSP server on an unrelated LSP settings change"
15672    );
15673
15674    update_test_project_settings(cx, |project_settings| {
15675        project_settings.lsp.insert(
15676            language_server_name.into(),
15677            LspSettings {
15678                binary: None,
15679                settings: None,
15680                initialization_options: Some(json!({
15681                    "anotherInitValue": false
15682                })),
15683                enable_lsp_tasks: false,
15684            },
15685        );
15686    });
15687    cx.executor().run_until_parked();
15688    assert_eq!(
15689        server_restarts.load(atomic::Ordering::Acquire),
15690        1,
15691        "Should restart LSP server on a related LSP settings change"
15692    );
15693
15694    update_test_project_settings(cx, |project_settings| {
15695        project_settings.lsp.insert(
15696            language_server_name.into(),
15697            LspSettings {
15698                binary: None,
15699                settings: None,
15700                initialization_options: Some(json!({
15701                    "anotherInitValue": false
15702                })),
15703                enable_lsp_tasks: false,
15704            },
15705        );
15706    });
15707    cx.executor().run_until_parked();
15708    assert_eq!(
15709        server_restarts.load(atomic::Ordering::Acquire),
15710        1,
15711        "Should not restart LSP server on a related LSP settings change that is the same"
15712    );
15713
15714    update_test_project_settings(cx, |project_settings| {
15715        project_settings.lsp.insert(
15716            language_server_name.into(),
15717            LspSettings {
15718                binary: None,
15719                settings: None,
15720                initialization_options: None,
15721                enable_lsp_tasks: false,
15722            },
15723        );
15724    });
15725    cx.executor().run_until_parked();
15726    assert_eq!(
15727        server_restarts.load(atomic::Ordering::Acquire),
15728        2,
15729        "Should restart LSP server on another related LSP settings change"
15730    );
15731}
15732
15733#[gpui::test]
15734async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15735    init_test(cx, |_| {});
15736
15737    let mut cx = EditorLspTestContext::new_rust(
15738        lsp::ServerCapabilities {
15739            completion_provider: Some(lsp::CompletionOptions {
15740                trigger_characters: Some(vec![".".to_string()]),
15741                resolve_provider: Some(true),
15742                ..Default::default()
15743            }),
15744            ..Default::default()
15745        },
15746        cx,
15747    )
15748    .await;
15749
15750    cx.set_state("fn main() { let a = 2ˇ; }");
15751    cx.simulate_keystroke(".");
15752    let completion_item = lsp::CompletionItem {
15753        label: "some".into(),
15754        kind: Some(lsp::CompletionItemKind::SNIPPET),
15755        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15756        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15757            kind: lsp::MarkupKind::Markdown,
15758            value: "```rust\nSome(2)\n```".to_string(),
15759        })),
15760        deprecated: Some(false),
15761        sort_text: Some("fffffff2".to_string()),
15762        filter_text: Some("some".to_string()),
15763        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15764        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15765            range: lsp::Range {
15766                start: lsp::Position {
15767                    line: 0,
15768                    character: 22,
15769                },
15770                end: lsp::Position {
15771                    line: 0,
15772                    character: 22,
15773                },
15774            },
15775            new_text: "Some(2)".to_string(),
15776        })),
15777        additional_text_edits: Some(vec![lsp::TextEdit {
15778            range: lsp::Range {
15779                start: lsp::Position {
15780                    line: 0,
15781                    character: 20,
15782                },
15783                end: lsp::Position {
15784                    line: 0,
15785                    character: 22,
15786                },
15787            },
15788            new_text: "".to_string(),
15789        }]),
15790        ..Default::default()
15791    };
15792
15793    let closure_completion_item = completion_item.clone();
15794    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15795        let task_completion_item = closure_completion_item.clone();
15796        async move {
15797            Ok(Some(lsp::CompletionResponse::Array(vec![
15798                task_completion_item,
15799            ])))
15800        }
15801    });
15802
15803    request.next().await;
15804
15805    cx.condition(|editor, _| editor.context_menu_visible())
15806        .await;
15807    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15808        editor
15809            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15810            .unwrap()
15811    });
15812    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15813
15814    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15815        let task_completion_item = completion_item.clone();
15816        async move { Ok(task_completion_item) }
15817    })
15818    .next()
15819    .await
15820    .unwrap();
15821    apply_additional_edits.await.unwrap();
15822    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15823}
15824
15825#[gpui::test]
15826async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15827    init_test(cx, |_| {});
15828
15829    let mut cx = EditorLspTestContext::new_rust(
15830        lsp::ServerCapabilities {
15831            completion_provider: Some(lsp::CompletionOptions {
15832                trigger_characters: Some(vec![".".to_string()]),
15833                resolve_provider: Some(true),
15834                ..Default::default()
15835            }),
15836            ..Default::default()
15837        },
15838        cx,
15839    )
15840    .await;
15841
15842    cx.set_state("fn main() { let a = 2ˇ; }");
15843    cx.simulate_keystroke(".");
15844
15845    let item1 = lsp::CompletionItem {
15846        label: "method id()".to_string(),
15847        filter_text: Some("id".to_string()),
15848        detail: None,
15849        documentation: None,
15850        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15851            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15852            new_text: ".id".to_string(),
15853        })),
15854        ..lsp::CompletionItem::default()
15855    };
15856
15857    let item2 = lsp::CompletionItem {
15858        label: "other".to_string(),
15859        filter_text: Some("other".to_string()),
15860        detail: None,
15861        documentation: None,
15862        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15863            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15864            new_text: ".other".to_string(),
15865        })),
15866        ..lsp::CompletionItem::default()
15867    };
15868
15869    let item1 = item1.clone();
15870    cx.set_request_handler::<lsp::request::Completion, _, _>({
15871        let item1 = item1.clone();
15872        move |_, _, _| {
15873            let item1 = item1.clone();
15874            let item2 = item2.clone();
15875            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15876        }
15877    })
15878    .next()
15879    .await;
15880
15881    cx.condition(|editor, _| editor.context_menu_visible())
15882        .await;
15883    cx.update_editor(|editor, _, _| {
15884        let context_menu = editor.context_menu.borrow_mut();
15885        let context_menu = context_menu
15886            .as_ref()
15887            .expect("Should have the context menu deployed");
15888        match context_menu {
15889            CodeContextMenu::Completions(completions_menu) => {
15890                let completions = completions_menu.completions.borrow_mut();
15891                assert_eq!(
15892                    completions
15893                        .iter()
15894                        .map(|completion| &completion.label.text)
15895                        .collect::<Vec<_>>(),
15896                    vec!["method id()", "other"]
15897                )
15898            }
15899            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15900        }
15901    });
15902
15903    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15904        let item1 = item1.clone();
15905        move |_, item_to_resolve, _| {
15906            let item1 = item1.clone();
15907            async move {
15908                if item1 == item_to_resolve {
15909                    Ok(lsp::CompletionItem {
15910                        label: "method id()".to_string(),
15911                        filter_text: Some("id".to_string()),
15912                        detail: Some("Now resolved!".to_string()),
15913                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15914                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15915                            range: lsp::Range::new(
15916                                lsp::Position::new(0, 22),
15917                                lsp::Position::new(0, 22),
15918                            ),
15919                            new_text: ".id".to_string(),
15920                        })),
15921                        ..lsp::CompletionItem::default()
15922                    })
15923                } else {
15924                    Ok(item_to_resolve)
15925                }
15926            }
15927        }
15928    })
15929    .next()
15930    .await
15931    .unwrap();
15932    cx.run_until_parked();
15933
15934    cx.update_editor(|editor, window, cx| {
15935        editor.context_menu_next(&Default::default(), window, cx);
15936    });
15937
15938    cx.update_editor(|editor, _, _| {
15939        let context_menu = editor.context_menu.borrow_mut();
15940        let context_menu = context_menu
15941            .as_ref()
15942            .expect("Should have the context menu deployed");
15943        match context_menu {
15944            CodeContextMenu::Completions(completions_menu) => {
15945                let completions = completions_menu.completions.borrow_mut();
15946                assert_eq!(
15947                    completions
15948                        .iter()
15949                        .map(|completion| &completion.label.text)
15950                        .collect::<Vec<_>>(),
15951                    vec!["method id() Now resolved!", "other"],
15952                    "Should update first completion label, but not second as the filter text did not match."
15953                );
15954            }
15955            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15956        }
15957    });
15958}
15959
15960#[gpui::test]
15961async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15962    init_test(cx, |_| {});
15963    let mut cx = EditorLspTestContext::new_rust(
15964        lsp::ServerCapabilities {
15965            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15966            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15967            completion_provider: Some(lsp::CompletionOptions {
15968                resolve_provider: Some(true),
15969                ..Default::default()
15970            }),
15971            ..Default::default()
15972        },
15973        cx,
15974    )
15975    .await;
15976    cx.set_state(indoc! {"
15977        struct TestStruct {
15978            field: i32
15979        }
15980
15981        fn mainˇ() {
15982            let unused_var = 42;
15983            let test_struct = TestStruct { field: 42 };
15984        }
15985    "});
15986    let symbol_range = cx.lsp_range(indoc! {"
15987        struct TestStruct {
15988            field: i32
15989        }
15990
15991        «fn main»() {
15992            let unused_var = 42;
15993            let test_struct = TestStruct { field: 42 };
15994        }
15995    "});
15996    let mut hover_requests =
15997        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15998            Ok(Some(lsp::Hover {
15999                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16000                    kind: lsp::MarkupKind::Markdown,
16001                    value: "Function documentation".to_string(),
16002                }),
16003                range: Some(symbol_range),
16004            }))
16005        });
16006
16007    // Case 1: Test that code action menu hide hover popover
16008    cx.dispatch_action(Hover);
16009    hover_requests.next().await;
16010    cx.condition(|editor, _| editor.hover_state.visible()).await;
16011    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16012        move |_, _, _| async move {
16013            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16014                lsp::CodeAction {
16015                    title: "Remove unused variable".to_string(),
16016                    kind: Some(CodeActionKind::QUICKFIX),
16017                    edit: Some(lsp::WorkspaceEdit {
16018                        changes: Some(
16019                            [(
16020                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
16021                                vec![lsp::TextEdit {
16022                                    range: lsp::Range::new(
16023                                        lsp::Position::new(5, 4),
16024                                        lsp::Position::new(5, 27),
16025                                    ),
16026                                    new_text: "".to_string(),
16027                                }],
16028                            )]
16029                            .into_iter()
16030                            .collect(),
16031                        ),
16032                        ..Default::default()
16033                    }),
16034                    ..Default::default()
16035                },
16036            )]))
16037        },
16038    );
16039    cx.update_editor(|editor, window, cx| {
16040        editor.toggle_code_actions(
16041            &ToggleCodeActions {
16042                deployed_from: None,
16043                quick_launch: false,
16044            },
16045            window,
16046            cx,
16047        );
16048    });
16049    code_action_requests.next().await;
16050    cx.run_until_parked();
16051    cx.condition(|editor, _| editor.context_menu_visible())
16052        .await;
16053    cx.update_editor(|editor, _, _| {
16054        assert!(
16055            !editor.hover_state.visible(),
16056            "Hover popover should be hidden when code action menu is shown"
16057        );
16058        // Hide code actions
16059        editor.context_menu.take();
16060    });
16061
16062    // Case 2: Test that code completions hide hover popover
16063    cx.dispatch_action(Hover);
16064    hover_requests.next().await;
16065    cx.condition(|editor, _| editor.hover_state.visible()).await;
16066    let counter = Arc::new(AtomicUsize::new(0));
16067    let mut completion_requests =
16068        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16069            let counter = counter.clone();
16070            async move {
16071                counter.fetch_add(1, atomic::Ordering::Release);
16072                Ok(Some(lsp::CompletionResponse::Array(vec![
16073                    lsp::CompletionItem {
16074                        label: "main".into(),
16075                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16076                        detail: Some("() -> ()".to_string()),
16077                        ..Default::default()
16078                    },
16079                    lsp::CompletionItem {
16080                        label: "TestStruct".into(),
16081                        kind: Some(lsp::CompletionItemKind::STRUCT),
16082                        detail: Some("struct TestStruct".to_string()),
16083                        ..Default::default()
16084                    },
16085                ])))
16086            }
16087        });
16088    cx.update_editor(|editor, window, cx| {
16089        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16090    });
16091    completion_requests.next().await;
16092    cx.condition(|editor, _| editor.context_menu_visible())
16093        .await;
16094    cx.update_editor(|editor, _, _| {
16095        assert!(
16096            !editor.hover_state.visible(),
16097            "Hover popover should be hidden when completion menu is shown"
16098        );
16099    });
16100}
16101
16102#[gpui::test]
16103async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16104    init_test(cx, |_| {});
16105
16106    let mut cx = EditorLspTestContext::new_rust(
16107        lsp::ServerCapabilities {
16108            completion_provider: Some(lsp::CompletionOptions {
16109                trigger_characters: Some(vec![".".to_string()]),
16110                resolve_provider: Some(true),
16111                ..Default::default()
16112            }),
16113            ..Default::default()
16114        },
16115        cx,
16116    )
16117    .await;
16118
16119    cx.set_state("fn main() { let a = 2ˇ; }");
16120    cx.simulate_keystroke(".");
16121
16122    let unresolved_item_1 = lsp::CompletionItem {
16123        label: "id".to_string(),
16124        filter_text: Some("id".to_string()),
16125        detail: None,
16126        documentation: None,
16127        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16128            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16129            new_text: ".id".to_string(),
16130        })),
16131        ..lsp::CompletionItem::default()
16132    };
16133    let resolved_item_1 = lsp::CompletionItem {
16134        additional_text_edits: Some(vec![lsp::TextEdit {
16135            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16136            new_text: "!!".to_string(),
16137        }]),
16138        ..unresolved_item_1.clone()
16139    };
16140    let unresolved_item_2 = lsp::CompletionItem {
16141        label: "other".to_string(),
16142        filter_text: Some("other".to_string()),
16143        detail: None,
16144        documentation: None,
16145        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16146            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16147            new_text: ".other".to_string(),
16148        })),
16149        ..lsp::CompletionItem::default()
16150    };
16151    let resolved_item_2 = lsp::CompletionItem {
16152        additional_text_edits: Some(vec![lsp::TextEdit {
16153            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16154            new_text: "??".to_string(),
16155        }]),
16156        ..unresolved_item_2.clone()
16157    };
16158
16159    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16160    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16161    cx.lsp
16162        .server
16163        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16164            let unresolved_item_1 = unresolved_item_1.clone();
16165            let resolved_item_1 = resolved_item_1.clone();
16166            let unresolved_item_2 = unresolved_item_2.clone();
16167            let resolved_item_2 = resolved_item_2.clone();
16168            let resolve_requests_1 = resolve_requests_1.clone();
16169            let resolve_requests_2 = resolve_requests_2.clone();
16170            move |unresolved_request, _| {
16171                let unresolved_item_1 = unresolved_item_1.clone();
16172                let resolved_item_1 = resolved_item_1.clone();
16173                let unresolved_item_2 = unresolved_item_2.clone();
16174                let resolved_item_2 = resolved_item_2.clone();
16175                let resolve_requests_1 = resolve_requests_1.clone();
16176                let resolve_requests_2 = resolve_requests_2.clone();
16177                async move {
16178                    if unresolved_request == unresolved_item_1 {
16179                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16180                        Ok(resolved_item_1.clone())
16181                    } else if unresolved_request == unresolved_item_2 {
16182                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16183                        Ok(resolved_item_2.clone())
16184                    } else {
16185                        panic!("Unexpected completion item {unresolved_request:?}")
16186                    }
16187                }
16188            }
16189        })
16190        .detach();
16191
16192    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16193        let unresolved_item_1 = unresolved_item_1.clone();
16194        let unresolved_item_2 = unresolved_item_2.clone();
16195        async move {
16196            Ok(Some(lsp::CompletionResponse::Array(vec![
16197                unresolved_item_1,
16198                unresolved_item_2,
16199            ])))
16200        }
16201    })
16202    .next()
16203    .await;
16204
16205    cx.condition(|editor, _| editor.context_menu_visible())
16206        .await;
16207    cx.update_editor(|editor, _, _| {
16208        let context_menu = editor.context_menu.borrow_mut();
16209        let context_menu = context_menu
16210            .as_ref()
16211            .expect("Should have the context menu deployed");
16212        match context_menu {
16213            CodeContextMenu::Completions(completions_menu) => {
16214                let completions = completions_menu.completions.borrow_mut();
16215                assert_eq!(
16216                    completions
16217                        .iter()
16218                        .map(|completion| &completion.label.text)
16219                        .collect::<Vec<_>>(),
16220                    vec!["id", "other"]
16221                )
16222            }
16223            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16224        }
16225    });
16226    cx.run_until_parked();
16227
16228    cx.update_editor(|editor, window, cx| {
16229        editor.context_menu_next(&ContextMenuNext, window, cx);
16230    });
16231    cx.run_until_parked();
16232    cx.update_editor(|editor, window, cx| {
16233        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16234    });
16235    cx.run_until_parked();
16236    cx.update_editor(|editor, window, cx| {
16237        editor.context_menu_next(&ContextMenuNext, window, cx);
16238    });
16239    cx.run_until_parked();
16240    cx.update_editor(|editor, window, cx| {
16241        editor
16242            .compose_completion(&ComposeCompletion::default(), window, cx)
16243            .expect("No task returned")
16244    })
16245    .await
16246    .expect("Completion failed");
16247    cx.run_until_parked();
16248
16249    cx.update_editor(|editor, _, cx| {
16250        assert_eq!(
16251            resolve_requests_1.load(atomic::Ordering::Acquire),
16252            1,
16253            "Should always resolve once despite multiple selections"
16254        );
16255        assert_eq!(
16256            resolve_requests_2.load(atomic::Ordering::Acquire),
16257            1,
16258            "Should always resolve once after multiple selections and applying the completion"
16259        );
16260        assert_eq!(
16261            editor.text(cx),
16262            "fn main() { let a = ??.other; }",
16263            "Should use resolved data when applying the completion"
16264        );
16265    });
16266}
16267
16268#[gpui::test]
16269async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16270    init_test(cx, |_| {});
16271
16272    let item_0 = lsp::CompletionItem {
16273        label: "abs".into(),
16274        insert_text: Some("abs".into()),
16275        data: Some(json!({ "very": "special"})),
16276        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16277        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16278            lsp::InsertReplaceEdit {
16279                new_text: "abs".to_string(),
16280                insert: lsp::Range::default(),
16281                replace: lsp::Range::default(),
16282            },
16283        )),
16284        ..lsp::CompletionItem::default()
16285    };
16286    let items = iter::once(item_0.clone())
16287        .chain((11..51).map(|i| lsp::CompletionItem {
16288            label: format!("item_{}", i),
16289            insert_text: Some(format!("item_{}", i)),
16290            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16291            ..lsp::CompletionItem::default()
16292        }))
16293        .collect::<Vec<_>>();
16294
16295    let default_commit_characters = vec!["?".to_string()];
16296    let default_data = json!({ "default": "data"});
16297    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16298    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16299    let default_edit_range = lsp::Range {
16300        start: lsp::Position {
16301            line: 0,
16302            character: 5,
16303        },
16304        end: lsp::Position {
16305            line: 0,
16306            character: 5,
16307        },
16308    };
16309
16310    let mut cx = EditorLspTestContext::new_rust(
16311        lsp::ServerCapabilities {
16312            completion_provider: Some(lsp::CompletionOptions {
16313                trigger_characters: Some(vec![".".to_string()]),
16314                resolve_provider: Some(true),
16315                ..Default::default()
16316            }),
16317            ..Default::default()
16318        },
16319        cx,
16320    )
16321    .await;
16322
16323    cx.set_state("fn main() { let a = 2ˇ; }");
16324    cx.simulate_keystroke(".");
16325
16326    let completion_data = default_data.clone();
16327    let completion_characters = default_commit_characters.clone();
16328    let completion_items = items.clone();
16329    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16330        let default_data = completion_data.clone();
16331        let default_commit_characters = completion_characters.clone();
16332        let items = completion_items.clone();
16333        async move {
16334            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16335                items,
16336                item_defaults: Some(lsp::CompletionListItemDefaults {
16337                    data: Some(default_data.clone()),
16338                    commit_characters: Some(default_commit_characters.clone()),
16339                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16340                        default_edit_range,
16341                    )),
16342                    insert_text_format: Some(default_insert_text_format),
16343                    insert_text_mode: Some(default_insert_text_mode),
16344                }),
16345                ..lsp::CompletionList::default()
16346            })))
16347        }
16348    })
16349    .next()
16350    .await;
16351
16352    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16353    cx.lsp
16354        .server
16355        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16356            let closure_resolved_items = resolved_items.clone();
16357            move |item_to_resolve, _| {
16358                let closure_resolved_items = closure_resolved_items.clone();
16359                async move {
16360                    closure_resolved_items.lock().push(item_to_resolve.clone());
16361                    Ok(item_to_resolve)
16362                }
16363            }
16364        })
16365        .detach();
16366
16367    cx.condition(|editor, _| editor.context_menu_visible())
16368        .await;
16369    cx.run_until_parked();
16370    cx.update_editor(|editor, _, _| {
16371        let menu = editor.context_menu.borrow_mut();
16372        match menu.as_ref().expect("should have the completions menu") {
16373            CodeContextMenu::Completions(completions_menu) => {
16374                assert_eq!(
16375                    completions_menu
16376                        .entries
16377                        .borrow()
16378                        .iter()
16379                        .map(|mat| mat.string.clone())
16380                        .collect::<Vec<String>>(),
16381                    items
16382                        .iter()
16383                        .map(|completion| completion.label.clone())
16384                        .collect::<Vec<String>>()
16385                );
16386            }
16387            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16388        }
16389    });
16390    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16391    // with 4 from the end.
16392    assert_eq!(
16393        *resolved_items.lock(),
16394        [&items[0..16], &items[items.len() - 4..items.len()]]
16395            .concat()
16396            .iter()
16397            .cloned()
16398            .map(|mut item| {
16399                if item.data.is_none() {
16400                    item.data = Some(default_data.clone());
16401                }
16402                item
16403            })
16404            .collect::<Vec<lsp::CompletionItem>>(),
16405        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16406    );
16407    resolved_items.lock().clear();
16408
16409    cx.update_editor(|editor, window, cx| {
16410        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16411    });
16412    cx.run_until_parked();
16413    // Completions that have already been resolved are skipped.
16414    assert_eq!(
16415        *resolved_items.lock(),
16416        items[items.len() - 17..items.len() - 4]
16417            .iter()
16418            .cloned()
16419            .map(|mut item| {
16420                if item.data.is_none() {
16421                    item.data = Some(default_data.clone());
16422                }
16423                item
16424            })
16425            .collect::<Vec<lsp::CompletionItem>>()
16426    );
16427    resolved_items.lock().clear();
16428}
16429
16430#[gpui::test]
16431async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16432    init_test(cx, |_| {});
16433
16434    let mut cx = EditorLspTestContext::new(
16435        Language::new(
16436            LanguageConfig {
16437                matcher: LanguageMatcher {
16438                    path_suffixes: vec!["jsx".into()],
16439                    ..Default::default()
16440                },
16441                overrides: [(
16442                    "element".into(),
16443                    LanguageConfigOverride {
16444                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16445                        ..Default::default()
16446                    },
16447                )]
16448                .into_iter()
16449                .collect(),
16450                ..Default::default()
16451            },
16452            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16453        )
16454        .with_override_query("(jsx_self_closing_element) @element")
16455        .unwrap(),
16456        lsp::ServerCapabilities {
16457            completion_provider: Some(lsp::CompletionOptions {
16458                trigger_characters: Some(vec![":".to_string()]),
16459                ..Default::default()
16460            }),
16461            ..Default::default()
16462        },
16463        cx,
16464    )
16465    .await;
16466
16467    cx.lsp
16468        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16469            Ok(Some(lsp::CompletionResponse::Array(vec![
16470                lsp::CompletionItem {
16471                    label: "bg-blue".into(),
16472                    ..Default::default()
16473                },
16474                lsp::CompletionItem {
16475                    label: "bg-red".into(),
16476                    ..Default::default()
16477                },
16478                lsp::CompletionItem {
16479                    label: "bg-yellow".into(),
16480                    ..Default::default()
16481                },
16482            ])))
16483        });
16484
16485    cx.set_state(r#"<p class="bgˇ" />"#);
16486
16487    // Trigger completion when typing a dash, because the dash is an extra
16488    // word character in the 'element' scope, which contains the cursor.
16489    cx.simulate_keystroke("-");
16490    cx.executor().run_until_parked();
16491    cx.update_editor(|editor, _, _| {
16492        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16493        {
16494            assert_eq!(
16495                completion_menu_entries(&menu),
16496                &["bg-blue", "bg-red", "bg-yellow"]
16497            );
16498        } else {
16499            panic!("expected completion menu to be open");
16500        }
16501    });
16502
16503    cx.simulate_keystroke("l");
16504    cx.executor().run_until_parked();
16505    cx.update_editor(|editor, _, _| {
16506        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16507        {
16508            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16509        } else {
16510            panic!("expected completion menu to be open");
16511        }
16512    });
16513
16514    // When filtering completions, consider the character after the '-' to
16515    // be the start of a subword.
16516    cx.set_state(r#"<p class="yelˇ" />"#);
16517    cx.simulate_keystroke("l");
16518    cx.executor().run_until_parked();
16519    cx.update_editor(|editor, _, _| {
16520        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16521        {
16522            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16523        } else {
16524            panic!("expected completion menu to be open");
16525        }
16526    });
16527}
16528
16529fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16530    let entries = menu.entries.borrow();
16531    entries.iter().map(|mat| mat.string.clone()).collect()
16532}
16533
16534#[gpui::test]
16535async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16536    init_test(cx, |settings| {
16537        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16538            Formatter::Prettier,
16539        )))
16540    });
16541
16542    let fs = FakeFs::new(cx.executor());
16543    fs.insert_file(path!("/file.ts"), Default::default()).await;
16544
16545    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16546    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16547
16548    language_registry.add(Arc::new(Language::new(
16549        LanguageConfig {
16550            name: "TypeScript".into(),
16551            matcher: LanguageMatcher {
16552                path_suffixes: vec!["ts".to_string()],
16553                ..Default::default()
16554            },
16555            ..Default::default()
16556        },
16557        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16558    )));
16559    update_test_language_settings(cx, |settings| {
16560        settings.defaults.prettier = Some(PrettierSettings {
16561            allowed: true,
16562            ..PrettierSettings::default()
16563        });
16564    });
16565
16566    let test_plugin = "test_plugin";
16567    let _ = language_registry.register_fake_lsp(
16568        "TypeScript",
16569        FakeLspAdapter {
16570            prettier_plugins: vec![test_plugin],
16571            ..Default::default()
16572        },
16573    );
16574
16575    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16576    let buffer = project
16577        .update(cx, |project, cx| {
16578            project.open_local_buffer(path!("/file.ts"), cx)
16579        })
16580        .await
16581        .unwrap();
16582
16583    let buffer_text = "one\ntwo\nthree\n";
16584    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16585    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16586    editor.update_in(cx, |editor, window, cx| {
16587        editor.set_text(buffer_text, window, cx)
16588    });
16589
16590    editor
16591        .update_in(cx, |editor, window, cx| {
16592            editor.perform_format(
16593                project.clone(),
16594                FormatTrigger::Manual,
16595                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16596                window,
16597                cx,
16598            )
16599        })
16600        .unwrap()
16601        .await;
16602    assert_eq!(
16603        editor.update(cx, |editor, cx| editor.text(cx)),
16604        buffer_text.to_string() + prettier_format_suffix,
16605        "Test prettier formatting was not applied to the original buffer text",
16606    );
16607
16608    update_test_language_settings(cx, |settings| {
16609        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16610    });
16611    let format = editor.update_in(cx, |editor, window, cx| {
16612        editor.perform_format(
16613            project.clone(),
16614            FormatTrigger::Manual,
16615            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16616            window,
16617            cx,
16618        )
16619    });
16620    format.await.unwrap();
16621    assert_eq!(
16622        editor.update(cx, |editor, cx| editor.text(cx)),
16623        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16624        "Autoformatting (via test prettier) was not applied to the original buffer text",
16625    );
16626}
16627
16628#[gpui::test]
16629async fn test_addition_reverts(cx: &mut TestAppContext) {
16630    init_test(cx, |_| {});
16631    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16632    let base_text = indoc! {r#"
16633        struct Row;
16634        struct Row1;
16635        struct Row2;
16636
16637        struct Row4;
16638        struct Row5;
16639        struct Row6;
16640
16641        struct Row8;
16642        struct Row9;
16643        struct Row10;"#};
16644
16645    // When addition hunks are not adjacent to carets, no hunk revert is performed
16646    assert_hunk_revert(
16647        indoc! {r#"struct Row;
16648                   struct Row1;
16649                   struct Row1.1;
16650                   struct Row1.2;
16651                   struct Row2;ˇ
16652
16653                   struct Row4;
16654                   struct Row5;
16655                   struct Row6;
16656
16657                   struct Row8;
16658                   ˇstruct Row9;
16659                   struct Row9.1;
16660                   struct Row9.2;
16661                   struct Row9.3;
16662                   struct Row10;"#},
16663        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16664        indoc! {r#"struct Row;
16665                   struct Row1;
16666                   struct Row1.1;
16667                   struct Row1.2;
16668                   struct Row2;ˇ
16669
16670                   struct Row4;
16671                   struct Row5;
16672                   struct Row6;
16673
16674                   struct Row8;
16675                   ˇstruct Row9;
16676                   struct Row9.1;
16677                   struct Row9.2;
16678                   struct Row9.3;
16679                   struct Row10;"#},
16680        base_text,
16681        &mut cx,
16682    );
16683    // Same for selections
16684    assert_hunk_revert(
16685        indoc! {r#"struct Row;
16686                   struct Row1;
16687                   struct Row2;
16688                   struct Row2.1;
16689                   struct Row2.2;
16690                   «ˇ
16691                   struct Row4;
16692                   struct» Row5;
16693                   «struct Row6;
16694                   ˇ»
16695                   struct Row9.1;
16696                   struct Row9.2;
16697                   struct Row9.3;
16698                   struct Row8;
16699                   struct Row9;
16700                   struct Row10;"#},
16701        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16702        indoc! {r#"struct Row;
16703                   struct Row1;
16704                   struct Row2;
16705                   struct Row2.1;
16706                   struct Row2.2;
16707                   «ˇ
16708                   struct Row4;
16709                   struct» Row5;
16710                   «struct Row6;
16711                   ˇ»
16712                   struct Row9.1;
16713                   struct Row9.2;
16714                   struct Row9.3;
16715                   struct Row8;
16716                   struct Row9;
16717                   struct Row10;"#},
16718        base_text,
16719        &mut cx,
16720    );
16721
16722    // When carets and selections intersect the addition hunks, those are reverted.
16723    // Adjacent carets got merged.
16724    assert_hunk_revert(
16725        indoc! {r#"struct Row;
16726                   ˇ// something on the top
16727                   struct Row1;
16728                   struct Row2;
16729                   struct Roˇw3.1;
16730                   struct Row2.2;
16731                   struct Row2.3;ˇ
16732
16733                   struct Row4;
16734                   struct ˇRow5.1;
16735                   struct Row5.2;
16736                   struct «Rowˇ»5.3;
16737                   struct Row5;
16738                   struct Row6;
16739                   ˇ
16740                   struct Row9.1;
16741                   struct «Rowˇ»9.2;
16742                   struct «ˇRow»9.3;
16743                   struct Row8;
16744                   struct Row9;
16745                   «ˇ// something on bottom»
16746                   struct Row10;"#},
16747        vec![
16748            DiffHunkStatusKind::Added,
16749            DiffHunkStatusKind::Added,
16750            DiffHunkStatusKind::Added,
16751            DiffHunkStatusKind::Added,
16752            DiffHunkStatusKind::Added,
16753        ],
16754        indoc! {r#"struct Row;
16755                   ˇstruct Row1;
16756                   struct Row2;
16757                   ˇ
16758                   struct Row4;
16759                   ˇstruct Row5;
16760                   struct Row6;
16761                   ˇ
16762                   ˇstruct Row8;
16763                   struct Row9;
16764                   ˇstruct Row10;"#},
16765        base_text,
16766        &mut cx,
16767    );
16768}
16769
16770#[gpui::test]
16771async fn test_modification_reverts(cx: &mut TestAppContext) {
16772    init_test(cx, |_| {});
16773    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16774    let base_text = indoc! {r#"
16775        struct Row;
16776        struct Row1;
16777        struct Row2;
16778
16779        struct Row4;
16780        struct Row5;
16781        struct Row6;
16782
16783        struct Row8;
16784        struct Row9;
16785        struct Row10;"#};
16786
16787    // Modification hunks behave the same as the addition ones.
16788    assert_hunk_revert(
16789        indoc! {r#"struct Row;
16790                   struct Row1;
16791                   struct Row33;
16792                   ˇ
16793                   struct Row4;
16794                   struct Row5;
16795                   struct Row6;
16796                   ˇ
16797                   struct Row99;
16798                   struct Row9;
16799                   struct Row10;"#},
16800        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16801        indoc! {r#"struct Row;
16802                   struct Row1;
16803                   struct Row33;
16804                   ˇ
16805                   struct Row4;
16806                   struct Row5;
16807                   struct Row6;
16808                   ˇ
16809                   struct Row99;
16810                   struct Row9;
16811                   struct Row10;"#},
16812        base_text,
16813        &mut cx,
16814    );
16815    assert_hunk_revert(
16816        indoc! {r#"struct Row;
16817                   struct Row1;
16818                   struct Row33;
16819                   «ˇ
16820                   struct Row4;
16821                   struct» Row5;
16822                   «struct Row6;
16823                   ˇ»
16824                   struct Row99;
16825                   struct Row9;
16826                   struct Row10;"#},
16827        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16828        indoc! {r#"struct Row;
16829                   struct Row1;
16830                   struct Row33;
16831                   «ˇ
16832                   struct Row4;
16833                   struct» Row5;
16834                   «struct Row6;
16835                   ˇ»
16836                   struct Row99;
16837                   struct Row9;
16838                   struct Row10;"#},
16839        base_text,
16840        &mut cx,
16841    );
16842
16843    assert_hunk_revert(
16844        indoc! {r#"ˇstruct Row1.1;
16845                   struct Row1;
16846                   «ˇstr»uct Row22;
16847
16848                   struct ˇRow44;
16849                   struct Row5;
16850                   struct «Rˇ»ow66;ˇ
16851
16852                   «struˇ»ct Row88;
16853                   struct Row9;
16854                   struct Row1011;ˇ"#},
16855        vec![
16856            DiffHunkStatusKind::Modified,
16857            DiffHunkStatusKind::Modified,
16858            DiffHunkStatusKind::Modified,
16859            DiffHunkStatusKind::Modified,
16860            DiffHunkStatusKind::Modified,
16861            DiffHunkStatusKind::Modified,
16862        ],
16863        indoc! {r#"struct Row;
16864                   ˇstruct Row1;
16865                   struct Row2;
16866                   ˇ
16867                   struct Row4;
16868                   ˇstruct Row5;
16869                   struct Row6;
16870                   ˇ
16871                   struct Row8;
16872                   ˇstruct Row9;
16873                   struct Row10;ˇ"#},
16874        base_text,
16875        &mut cx,
16876    );
16877}
16878
16879#[gpui::test]
16880async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16881    init_test(cx, |_| {});
16882    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16883    let base_text = indoc! {r#"
16884        one
16885
16886        two
16887        three
16888        "#};
16889
16890    cx.set_head_text(base_text);
16891    cx.set_state("\nˇ\n");
16892    cx.executor().run_until_parked();
16893    cx.update_editor(|editor, _window, cx| {
16894        editor.expand_selected_diff_hunks(cx);
16895    });
16896    cx.executor().run_until_parked();
16897    cx.update_editor(|editor, window, cx| {
16898        editor.backspace(&Default::default(), window, cx);
16899    });
16900    cx.run_until_parked();
16901    cx.assert_state_with_diff(
16902        indoc! {r#"
16903
16904        - two
16905        - threeˇ
16906        +
16907        "#}
16908        .to_string(),
16909    );
16910}
16911
16912#[gpui::test]
16913async fn test_deletion_reverts(cx: &mut TestAppContext) {
16914    init_test(cx, |_| {});
16915    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16916    let base_text = indoc! {r#"struct Row;
16917struct Row1;
16918struct Row2;
16919
16920struct Row4;
16921struct Row5;
16922struct Row6;
16923
16924struct Row8;
16925struct Row9;
16926struct Row10;"#};
16927
16928    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16929    assert_hunk_revert(
16930        indoc! {r#"struct Row;
16931                   struct Row2;
16932
16933                   ˇstruct Row4;
16934                   struct Row5;
16935                   struct Row6;
16936                   ˇ
16937                   struct Row8;
16938                   struct Row10;"#},
16939        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16940        indoc! {r#"struct Row;
16941                   struct Row2;
16942
16943                   ˇstruct Row4;
16944                   struct Row5;
16945                   struct Row6;
16946                   ˇ
16947                   struct Row8;
16948                   struct Row10;"#},
16949        base_text,
16950        &mut cx,
16951    );
16952    assert_hunk_revert(
16953        indoc! {r#"struct Row;
16954                   struct Row2;
16955
16956                   «ˇstruct Row4;
16957                   struct» Row5;
16958                   «struct Row6;
16959                   ˇ»
16960                   struct Row8;
16961                   struct Row10;"#},
16962        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16963        indoc! {r#"struct Row;
16964                   struct Row2;
16965
16966                   «ˇstruct Row4;
16967                   struct» Row5;
16968                   «struct Row6;
16969                   ˇ»
16970                   struct Row8;
16971                   struct Row10;"#},
16972        base_text,
16973        &mut cx,
16974    );
16975
16976    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16977    assert_hunk_revert(
16978        indoc! {r#"struct Row;
16979                   ˇstruct Row2;
16980
16981                   struct Row4;
16982                   struct Row5;
16983                   struct Row6;
16984
16985                   struct Row8;ˇ
16986                   struct Row10;"#},
16987        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16988        indoc! {r#"struct Row;
16989                   struct Row1;
16990                   ˇstruct Row2;
16991
16992                   struct Row4;
16993                   struct Row5;
16994                   struct Row6;
16995
16996                   struct Row8;ˇ
16997                   struct Row9;
16998                   struct Row10;"#},
16999        base_text,
17000        &mut cx,
17001    );
17002    assert_hunk_revert(
17003        indoc! {r#"struct Row;
17004                   struct Row2«ˇ;
17005                   struct Row4;
17006                   struct» Row5;
17007                   «struct Row6;
17008
17009                   struct Row8;ˇ»
17010                   struct Row10;"#},
17011        vec![
17012            DiffHunkStatusKind::Deleted,
17013            DiffHunkStatusKind::Deleted,
17014            DiffHunkStatusKind::Deleted,
17015        ],
17016        indoc! {r#"struct Row;
17017                   struct Row1;
17018                   struct Row2«ˇ;
17019
17020                   struct Row4;
17021                   struct» Row5;
17022                   «struct Row6;
17023
17024                   struct Row8;ˇ»
17025                   struct Row9;
17026                   struct Row10;"#},
17027        base_text,
17028        &mut cx,
17029    );
17030}
17031
17032#[gpui::test]
17033async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17034    init_test(cx, |_| {});
17035
17036    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17037    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17038    let base_text_3 =
17039        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17040
17041    let text_1 = edit_first_char_of_every_line(base_text_1);
17042    let text_2 = edit_first_char_of_every_line(base_text_2);
17043    let text_3 = edit_first_char_of_every_line(base_text_3);
17044
17045    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17046    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17047    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17048
17049    let multibuffer = cx.new(|cx| {
17050        let mut multibuffer = MultiBuffer::new(ReadWrite);
17051        multibuffer.push_excerpts(
17052            buffer_1.clone(),
17053            [
17054                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17055                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17056                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17057            ],
17058            cx,
17059        );
17060        multibuffer.push_excerpts(
17061            buffer_2.clone(),
17062            [
17063                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17064                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17065                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17066            ],
17067            cx,
17068        );
17069        multibuffer.push_excerpts(
17070            buffer_3.clone(),
17071            [
17072                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17073                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17074                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17075            ],
17076            cx,
17077        );
17078        multibuffer
17079    });
17080
17081    let fs = FakeFs::new(cx.executor());
17082    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17083    let (editor, cx) = cx
17084        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17085    editor.update_in(cx, |editor, _window, cx| {
17086        for (buffer, diff_base) in [
17087            (buffer_1.clone(), base_text_1),
17088            (buffer_2.clone(), base_text_2),
17089            (buffer_3.clone(), base_text_3),
17090        ] {
17091            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17092            editor
17093                .buffer
17094                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17095        }
17096    });
17097    cx.executor().run_until_parked();
17098
17099    editor.update_in(cx, |editor, window, cx| {
17100        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}");
17101        editor.select_all(&SelectAll, window, cx);
17102        editor.git_restore(&Default::default(), window, cx);
17103    });
17104    cx.executor().run_until_parked();
17105
17106    // When all ranges are selected, all buffer hunks are reverted.
17107    editor.update(cx, |editor, cx| {
17108        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");
17109    });
17110    buffer_1.update(cx, |buffer, _| {
17111        assert_eq!(buffer.text(), base_text_1);
17112    });
17113    buffer_2.update(cx, |buffer, _| {
17114        assert_eq!(buffer.text(), base_text_2);
17115    });
17116    buffer_3.update(cx, |buffer, _| {
17117        assert_eq!(buffer.text(), base_text_3);
17118    });
17119
17120    editor.update_in(cx, |editor, window, cx| {
17121        editor.undo(&Default::default(), window, cx);
17122    });
17123
17124    editor.update_in(cx, |editor, window, cx| {
17125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17126            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17127        });
17128        editor.git_restore(&Default::default(), window, cx);
17129    });
17130
17131    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17132    // but not affect buffer_2 and its related excerpts.
17133    editor.update(cx, |editor, cx| {
17134        assert_eq!(
17135            editor.text(cx),
17136            "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}"
17137        );
17138    });
17139    buffer_1.update(cx, |buffer, _| {
17140        assert_eq!(buffer.text(), base_text_1);
17141    });
17142    buffer_2.update(cx, |buffer, _| {
17143        assert_eq!(
17144            buffer.text(),
17145            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17146        );
17147    });
17148    buffer_3.update(cx, |buffer, _| {
17149        assert_eq!(
17150            buffer.text(),
17151            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17152        );
17153    });
17154
17155    fn edit_first_char_of_every_line(text: &str) -> String {
17156        text.split('\n')
17157            .map(|line| format!("X{}", &line[1..]))
17158            .collect::<Vec<_>>()
17159            .join("\n")
17160    }
17161}
17162
17163#[gpui::test]
17164async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17165    init_test(cx, |_| {});
17166
17167    let cols = 4;
17168    let rows = 10;
17169    let sample_text_1 = sample_text(rows, cols, 'a');
17170    assert_eq!(
17171        sample_text_1,
17172        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17173    );
17174    let sample_text_2 = sample_text(rows, cols, 'l');
17175    assert_eq!(
17176        sample_text_2,
17177        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17178    );
17179    let sample_text_3 = sample_text(rows, cols, 'v');
17180    assert_eq!(
17181        sample_text_3,
17182        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17183    );
17184
17185    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17186    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17187    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17188
17189    let multi_buffer = cx.new(|cx| {
17190        let mut multibuffer = MultiBuffer::new(ReadWrite);
17191        multibuffer.push_excerpts(
17192            buffer_1.clone(),
17193            [
17194                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17195                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17196                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17197            ],
17198            cx,
17199        );
17200        multibuffer.push_excerpts(
17201            buffer_2.clone(),
17202            [
17203                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17204                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17205                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17206            ],
17207            cx,
17208        );
17209        multibuffer.push_excerpts(
17210            buffer_3.clone(),
17211            [
17212                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17213                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17214                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17215            ],
17216            cx,
17217        );
17218        multibuffer
17219    });
17220
17221    let fs = FakeFs::new(cx.executor());
17222    fs.insert_tree(
17223        "/a",
17224        json!({
17225            "main.rs": sample_text_1,
17226            "other.rs": sample_text_2,
17227            "lib.rs": sample_text_3,
17228        }),
17229    )
17230    .await;
17231    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17232    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17233    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17234    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17235        Editor::new(
17236            EditorMode::full(),
17237            multi_buffer,
17238            Some(project.clone()),
17239            window,
17240            cx,
17241        )
17242    });
17243    let multibuffer_item_id = workspace
17244        .update(cx, |workspace, window, cx| {
17245            assert!(
17246                workspace.active_item(cx).is_none(),
17247                "active item should be None before the first item is added"
17248            );
17249            workspace.add_item_to_active_pane(
17250                Box::new(multi_buffer_editor.clone()),
17251                None,
17252                true,
17253                window,
17254                cx,
17255            );
17256            let active_item = workspace
17257                .active_item(cx)
17258                .expect("should have an active item after adding the multi buffer");
17259            assert!(
17260                !active_item.is_singleton(cx),
17261                "A multi buffer was expected to active after adding"
17262            );
17263            active_item.item_id()
17264        })
17265        .unwrap();
17266    cx.executor().run_until_parked();
17267
17268    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17269        editor.change_selections(
17270            SelectionEffects::scroll(Autoscroll::Next),
17271            window,
17272            cx,
17273            |s| s.select_ranges(Some(1..2)),
17274        );
17275        editor.open_excerpts(&OpenExcerpts, window, cx);
17276    });
17277    cx.executor().run_until_parked();
17278    let first_item_id = workspace
17279        .update(cx, |workspace, window, cx| {
17280            let active_item = workspace
17281                .active_item(cx)
17282                .expect("should have an active item after navigating into the 1st buffer");
17283            let first_item_id = active_item.item_id();
17284            assert_ne!(
17285                first_item_id, multibuffer_item_id,
17286                "Should navigate into the 1st buffer and activate it"
17287            );
17288            assert!(
17289                active_item.is_singleton(cx),
17290                "New active item should be a singleton buffer"
17291            );
17292            assert_eq!(
17293                active_item
17294                    .act_as::<Editor>(cx)
17295                    .expect("should have navigated into an editor for the 1st buffer")
17296                    .read(cx)
17297                    .text(cx),
17298                sample_text_1
17299            );
17300
17301            workspace
17302                .go_back(workspace.active_pane().downgrade(), window, cx)
17303                .detach_and_log_err(cx);
17304
17305            first_item_id
17306        })
17307        .unwrap();
17308    cx.executor().run_until_parked();
17309    workspace
17310        .update(cx, |workspace, _, cx| {
17311            let active_item = workspace
17312                .active_item(cx)
17313                .expect("should have an active item after navigating back");
17314            assert_eq!(
17315                active_item.item_id(),
17316                multibuffer_item_id,
17317                "Should navigate back to the multi buffer"
17318            );
17319            assert!(!active_item.is_singleton(cx));
17320        })
17321        .unwrap();
17322
17323    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17324        editor.change_selections(
17325            SelectionEffects::scroll(Autoscroll::Next),
17326            window,
17327            cx,
17328            |s| s.select_ranges(Some(39..40)),
17329        );
17330        editor.open_excerpts(&OpenExcerpts, window, cx);
17331    });
17332    cx.executor().run_until_parked();
17333    let second_item_id = workspace
17334        .update(cx, |workspace, window, cx| {
17335            let active_item = workspace
17336                .active_item(cx)
17337                .expect("should have an active item after navigating into the 2nd buffer");
17338            let second_item_id = active_item.item_id();
17339            assert_ne!(
17340                second_item_id, multibuffer_item_id,
17341                "Should navigate away from the multibuffer"
17342            );
17343            assert_ne!(
17344                second_item_id, first_item_id,
17345                "Should navigate into the 2nd buffer and activate it"
17346            );
17347            assert!(
17348                active_item.is_singleton(cx),
17349                "New active item should be a singleton buffer"
17350            );
17351            assert_eq!(
17352                active_item
17353                    .act_as::<Editor>(cx)
17354                    .expect("should have navigated into an editor")
17355                    .read(cx)
17356                    .text(cx),
17357                sample_text_2
17358            );
17359
17360            workspace
17361                .go_back(workspace.active_pane().downgrade(), window, cx)
17362                .detach_and_log_err(cx);
17363
17364            second_item_id
17365        })
17366        .unwrap();
17367    cx.executor().run_until_parked();
17368    workspace
17369        .update(cx, |workspace, _, cx| {
17370            let active_item = workspace
17371                .active_item(cx)
17372                .expect("should have an active item after navigating back from the 2nd buffer");
17373            assert_eq!(
17374                active_item.item_id(),
17375                multibuffer_item_id,
17376                "Should navigate back from the 2nd buffer to the multi buffer"
17377            );
17378            assert!(!active_item.is_singleton(cx));
17379        })
17380        .unwrap();
17381
17382    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17383        editor.change_selections(
17384            SelectionEffects::scroll(Autoscroll::Next),
17385            window,
17386            cx,
17387            |s| s.select_ranges(Some(70..70)),
17388        );
17389        editor.open_excerpts(&OpenExcerpts, window, cx);
17390    });
17391    cx.executor().run_until_parked();
17392    workspace
17393        .update(cx, |workspace, window, cx| {
17394            let active_item = workspace
17395                .active_item(cx)
17396                .expect("should have an active item after navigating into the 3rd buffer");
17397            let third_item_id = active_item.item_id();
17398            assert_ne!(
17399                third_item_id, multibuffer_item_id,
17400                "Should navigate into the 3rd buffer and activate it"
17401            );
17402            assert_ne!(third_item_id, first_item_id);
17403            assert_ne!(third_item_id, second_item_id);
17404            assert!(
17405                active_item.is_singleton(cx),
17406                "New active item should be a singleton buffer"
17407            );
17408            assert_eq!(
17409                active_item
17410                    .act_as::<Editor>(cx)
17411                    .expect("should have navigated into an editor")
17412                    .read(cx)
17413                    .text(cx),
17414                sample_text_3
17415            );
17416
17417            workspace
17418                .go_back(workspace.active_pane().downgrade(), window, cx)
17419                .detach_and_log_err(cx);
17420        })
17421        .unwrap();
17422    cx.executor().run_until_parked();
17423    workspace
17424        .update(cx, |workspace, _, cx| {
17425            let active_item = workspace
17426                .active_item(cx)
17427                .expect("should have an active item after navigating back from the 3rd buffer");
17428            assert_eq!(
17429                active_item.item_id(),
17430                multibuffer_item_id,
17431                "Should navigate back from the 3rd buffer to the multi buffer"
17432            );
17433            assert!(!active_item.is_singleton(cx));
17434        })
17435        .unwrap();
17436}
17437
17438#[gpui::test]
17439async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17440    init_test(cx, |_| {});
17441
17442    let mut cx = EditorTestContext::new(cx).await;
17443
17444    let diff_base = r#"
17445        use some::mod;
17446
17447        const A: u32 = 42;
17448
17449        fn main() {
17450            println!("hello");
17451
17452            println!("world");
17453        }
17454        "#
17455    .unindent();
17456
17457    cx.set_state(
17458        &r#"
17459        use some::modified;
17460
17461        ˇ
17462        fn main() {
17463            println!("hello there");
17464
17465            println!("around the");
17466            println!("world");
17467        }
17468        "#
17469        .unindent(),
17470    );
17471
17472    cx.set_head_text(&diff_base);
17473    executor.run_until_parked();
17474
17475    cx.update_editor(|editor, window, cx| {
17476        editor.go_to_next_hunk(&GoToHunk, window, cx);
17477        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17478    });
17479    executor.run_until_parked();
17480    cx.assert_state_with_diff(
17481        r#"
17482          use some::modified;
17483
17484
17485          fn main() {
17486        -     println!("hello");
17487        + ˇ    println!("hello there");
17488
17489              println!("around the");
17490              println!("world");
17491          }
17492        "#
17493        .unindent(),
17494    );
17495
17496    cx.update_editor(|editor, window, cx| {
17497        for _ in 0..2 {
17498            editor.go_to_next_hunk(&GoToHunk, window, cx);
17499            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17500        }
17501    });
17502    executor.run_until_parked();
17503    cx.assert_state_with_diff(
17504        r#"
17505        - use some::mod;
17506        + ˇuse some::modified;
17507
17508
17509          fn main() {
17510        -     println!("hello");
17511        +     println!("hello there");
17512
17513        +     println!("around the");
17514              println!("world");
17515          }
17516        "#
17517        .unindent(),
17518    );
17519
17520    cx.update_editor(|editor, window, cx| {
17521        editor.go_to_next_hunk(&GoToHunk, window, cx);
17522        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17523    });
17524    executor.run_until_parked();
17525    cx.assert_state_with_diff(
17526        r#"
17527        - use some::mod;
17528        + use some::modified;
17529
17530        - const A: u32 = 42;
17531          ˇ
17532          fn main() {
17533        -     println!("hello");
17534        +     println!("hello there");
17535
17536        +     println!("around the");
17537              println!("world");
17538          }
17539        "#
17540        .unindent(),
17541    );
17542
17543    cx.update_editor(|editor, window, cx| {
17544        editor.cancel(&Cancel, window, cx);
17545    });
17546
17547    cx.assert_state_with_diff(
17548        r#"
17549          use some::modified;
17550
17551          ˇ
17552          fn main() {
17553              println!("hello there");
17554
17555              println!("around the");
17556              println!("world");
17557          }
17558        "#
17559        .unindent(),
17560    );
17561}
17562
17563#[gpui::test]
17564async fn test_diff_base_change_with_expanded_diff_hunks(
17565    executor: BackgroundExecutor,
17566    cx: &mut TestAppContext,
17567) {
17568    init_test(cx, |_| {});
17569
17570    let mut cx = EditorTestContext::new(cx).await;
17571
17572    let diff_base = r#"
17573        use some::mod1;
17574        use some::mod2;
17575
17576        const A: u32 = 42;
17577        const B: u32 = 42;
17578        const C: u32 = 42;
17579
17580        fn main() {
17581            println!("hello");
17582
17583            println!("world");
17584        }
17585        "#
17586    .unindent();
17587
17588    cx.set_state(
17589        &r#"
17590        use some::mod2;
17591
17592        const A: u32 = 42;
17593        const C: u32 = 42;
17594
17595        fn main(ˇ) {
17596            //println!("hello");
17597
17598            println!("world");
17599            //
17600            //
17601        }
17602        "#
17603        .unindent(),
17604    );
17605
17606    cx.set_head_text(&diff_base);
17607    executor.run_until_parked();
17608
17609    cx.update_editor(|editor, window, cx| {
17610        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17611    });
17612    executor.run_until_parked();
17613    cx.assert_state_with_diff(
17614        r#"
17615        - use some::mod1;
17616          use some::mod2;
17617
17618          const A: u32 = 42;
17619        - const B: u32 = 42;
17620          const C: u32 = 42;
17621
17622          fn main(ˇ) {
17623        -     println!("hello");
17624        +     //println!("hello");
17625
17626              println!("world");
17627        +     //
17628        +     //
17629          }
17630        "#
17631        .unindent(),
17632    );
17633
17634    cx.set_head_text("new diff base!");
17635    executor.run_until_parked();
17636    cx.assert_state_with_diff(
17637        r#"
17638        - new diff base!
17639        + use some::mod2;
17640        +
17641        + const A: u32 = 42;
17642        + const C: u32 = 42;
17643        +
17644        + fn main(ˇ) {
17645        +     //println!("hello");
17646        +
17647        +     println!("world");
17648        +     //
17649        +     //
17650        + }
17651        "#
17652        .unindent(),
17653    );
17654}
17655
17656#[gpui::test]
17657async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17658    init_test(cx, |_| {});
17659
17660    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17661    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17662    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17663    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17664    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17665    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17666
17667    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17668    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17669    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17670
17671    let multi_buffer = cx.new(|cx| {
17672        let mut multibuffer = MultiBuffer::new(ReadWrite);
17673        multibuffer.push_excerpts(
17674            buffer_1.clone(),
17675            [
17676                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17677                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17678                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17679            ],
17680            cx,
17681        );
17682        multibuffer.push_excerpts(
17683            buffer_2.clone(),
17684            [
17685                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17686                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17687                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17688            ],
17689            cx,
17690        );
17691        multibuffer.push_excerpts(
17692            buffer_3.clone(),
17693            [
17694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17697            ],
17698            cx,
17699        );
17700        multibuffer
17701    });
17702
17703    let editor =
17704        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17705    editor
17706        .update(cx, |editor, _window, cx| {
17707            for (buffer, diff_base) in [
17708                (buffer_1.clone(), file_1_old),
17709                (buffer_2.clone(), file_2_old),
17710                (buffer_3.clone(), file_3_old),
17711            ] {
17712                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17713                editor
17714                    .buffer
17715                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17716            }
17717        })
17718        .unwrap();
17719
17720    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17721    cx.run_until_parked();
17722
17723    cx.assert_editor_state(
17724        &"
17725            ˇaaa
17726            ccc
17727            ddd
17728
17729            ggg
17730            hhh
17731
17732
17733            lll
17734            mmm
17735            NNN
17736
17737            qqq
17738            rrr
17739
17740            uuu
17741            111
17742            222
17743            333
17744
17745            666
17746            777
17747
17748            000
17749            !!!"
17750        .unindent(),
17751    );
17752
17753    cx.update_editor(|editor, window, cx| {
17754        editor.select_all(&SelectAll, window, cx);
17755        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17756    });
17757    cx.executor().run_until_parked();
17758
17759    cx.assert_state_with_diff(
17760        "
17761            «aaa
17762          - bbb
17763            ccc
17764            ddd
17765
17766            ggg
17767            hhh
17768
17769
17770            lll
17771            mmm
17772          - nnn
17773          + NNN
17774
17775            qqq
17776            rrr
17777
17778            uuu
17779            111
17780            222
17781            333
17782
17783          + 666
17784            777
17785
17786            000
17787            !!!ˇ»"
17788            .unindent(),
17789    );
17790}
17791
17792#[gpui::test]
17793async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17794    init_test(cx, |_| {});
17795
17796    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17797    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17798
17799    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17800    let multi_buffer = cx.new(|cx| {
17801        let mut multibuffer = MultiBuffer::new(ReadWrite);
17802        multibuffer.push_excerpts(
17803            buffer.clone(),
17804            [
17805                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17806                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17807                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17808            ],
17809            cx,
17810        );
17811        multibuffer
17812    });
17813
17814    let editor =
17815        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17816    editor
17817        .update(cx, |editor, _window, cx| {
17818            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17819            editor
17820                .buffer
17821                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17822        })
17823        .unwrap();
17824
17825    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17826    cx.run_until_parked();
17827
17828    cx.update_editor(|editor, window, cx| {
17829        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17830    });
17831    cx.executor().run_until_parked();
17832
17833    // When the start of a hunk coincides with the start of its excerpt,
17834    // the hunk is expanded. When the start of a a hunk is earlier than
17835    // the start of its excerpt, the hunk is not expanded.
17836    cx.assert_state_with_diff(
17837        "
17838            ˇaaa
17839          - bbb
17840          + BBB
17841
17842          - ddd
17843          - eee
17844          + DDD
17845          + EEE
17846            fff
17847
17848            iii
17849        "
17850        .unindent(),
17851    );
17852}
17853
17854#[gpui::test]
17855async fn test_edits_around_expanded_insertion_hunks(
17856    executor: BackgroundExecutor,
17857    cx: &mut TestAppContext,
17858) {
17859    init_test(cx, |_| {});
17860
17861    let mut cx = EditorTestContext::new(cx).await;
17862
17863    let diff_base = r#"
17864        use some::mod1;
17865        use some::mod2;
17866
17867        const A: u32 = 42;
17868
17869        fn main() {
17870            println!("hello");
17871
17872            println!("world");
17873        }
17874        "#
17875    .unindent();
17876    executor.run_until_parked();
17877    cx.set_state(
17878        &r#"
17879        use some::mod1;
17880        use some::mod2;
17881
17882        const A: u32 = 42;
17883        const B: u32 = 42;
17884        const C: u32 = 42;
17885        ˇ
17886
17887        fn main() {
17888            println!("hello");
17889
17890            println!("world");
17891        }
17892        "#
17893        .unindent(),
17894    );
17895
17896    cx.set_head_text(&diff_base);
17897    executor.run_until_parked();
17898
17899    cx.update_editor(|editor, window, cx| {
17900        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17901    });
17902    executor.run_until_parked();
17903
17904    cx.assert_state_with_diff(
17905        r#"
17906        use some::mod1;
17907        use some::mod2;
17908
17909        const A: u32 = 42;
17910      + const B: u32 = 42;
17911      + const C: u32 = 42;
17912      + ˇ
17913
17914        fn main() {
17915            println!("hello");
17916
17917            println!("world");
17918        }
17919      "#
17920        .unindent(),
17921    );
17922
17923    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17924    executor.run_until_parked();
17925
17926    cx.assert_state_with_diff(
17927        r#"
17928        use some::mod1;
17929        use some::mod2;
17930
17931        const A: u32 = 42;
17932      + const B: u32 = 42;
17933      + const C: u32 = 42;
17934      + const D: u32 = 42;
17935      + ˇ
17936
17937        fn main() {
17938            println!("hello");
17939
17940            println!("world");
17941        }
17942      "#
17943        .unindent(),
17944    );
17945
17946    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17947    executor.run_until_parked();
17948
17949    cx.assert_state_with_diff(
17950        r#"
17951        use some::mod1;
17952        use some::mod2;
17953
17954        const A: u32 = 42;
17955      + const B: u32 = 42;
17956      + const C: u32 = 42;
17957      + const D: u32 = 42;
17958      + const E: u32 = 42;
17959      + ˇ
17960
17961        fn main() {
17962            println!("hello");
17963
17964            println!("world");
17965        }
17966      "#
17967        .unindent(),
17968    );
17969
17970    cx.update_editor(|editor, window, cx| {
17971        editor.delete_line(&DeleteLine, window, cx);
17972    });
17973    executor.run_until_parked();
17974
17975    cx.assert_state_with_diff(
17976        r#"
17977        use some::mod1;
17978        use some::mod2;
17979
17980        const A: u32 = 42;
17981      + const B: u32 = 42;
17982      + const C: u32 = 42;
17983      + const D: u32 = 42;
17984      + const E: u32 = 42;
17985        ˇ
17986        fn main() {
17987            println!("hello");
17988
17989            println!("world");
17990        }
17991      "#
17992        .unindent(),
17993    );
17994
17995    cx.update_editor(|editor, window, cx| {
17996        editor.move_up(&MoveUp, window, cx);
17997        editor.delete_line(&DeleteLine, window, cx);
17998        editor.move_up(&MoveUp, window, cx);
17999        editor.delete_line(&DeleteLine, window, cx);
18000        editor.move_up(&MoveUp, window, cx);
18001        editor.delete_line(&DeleteLine, window, cx);
18002    });
18003    executor.run_until_parked();
18004    cx.assert_state_with_diff(
18005        r#"
18006        use some::mod1;
18007        use some::mod2;
18008
18009        const A: u32 = 42;
18010      + const B: u32 = 42;
18011        ˇ
18012        fn main() {
18013            println!("hello");
18014
18015            println!("world");
18016        }
18017      "#
18018        .unindent(),
18019    );
18020
18021    cx.update_editor(|editor, window, cx| {
18022        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18023        editor.delete_line(&DeleteLine, window, cx);
18024    });
18025    executor.run_until_parked();
18026    cx.assert_state_with_diff(
18027        r#"
18028        ˇ
18029        fn main() {
18030            println!("hello");
18031
18032            println!("world");
18033        }
18034      "#
18035        .unindent(),
18036    );
18037}
18038
18039#[gpui::test]
18040async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18041    init_test(cx, |_| {});
18042
18043    let mut cx = EditorTestContext::new(cx).await;
18044    cx.set_head_text(indoc! { "
18045        one
18046        two
18047        three
18048        four
18049        five
18050        "
18051    });
18052    cx.set_state(indoc! { "
18053        one
18054        ˇthree
18055        five
18056    "});
18057    cx.run_until_parked();
18058    cx.update_editor(|editor, window, cx| {
18059        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18060    });
18061    cx.assert_state_with_diff(
18062        indoc! { "
18063        one
18064      - two
18065        ˇthree
18066      - four
18067        five
18068    "}
18069        .to_string(),
18070    );
18071    cx.update_editor(|editor, window, cx| {
18072        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18073    });
18074
18075    cx.assert_state_with_diff(
18076        indoc! { "
18077        one
18078        ˇthree
18079        five
18080    "}
18081        .to_string(),
18082    );
18083
18084    cx.set_state(indoc! { "
18085        one
18086        ˇTWO
18087        three
18088        four
18089        five
18090    "});
18091    cx.run_until_parked();
18092    cx.update_editor(|editor, window, cx| {
18093        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18094    });
18095
18096    cx.assert_state_with_diff(
18097        indoc! { "
18098            one
18099          - two
18100          + ˇTWO
18101            three
18102            four
18103            five
18104        "}
18105        .to_string(),
18106    );
18107    cx.update_editor(|editor, window, cx| {
18108        editor.move_up(&Default::default(), window, cx);
18109        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18110    });
18111    cx.assert_state_with_diff(
18112        indoc! { "
18113            one
18114            ˇTWO
18115            three
18116            four
18117            five
18118        "}
18119        .to_string(),
18120    );
18121}
18122
18123#[gpui::test]
18124async fn test_edits_around_expanded_deletion_hunks(
18125    executor: BackgroundExecutor,
18126    cx: &mut TestAppContext,
18127) {
18128    init_test(cx, |_| {});
18129
18130    let mut cx = EditorTestContext::new(cx).await;
18131
18132    let diff_base = r#"
18133        use some::mod1;
18134        use some::mod2;
18135
18136        const A: u32 = 42;
18137        const B: u32 = 42;
18138        const C: u32 = 42;
18139
18140
18141        fn main() {
18142            println!("hello");
18143
18144            println!("world");
18145        }
18146    "#
18147    .unindent();
18148    executor.run_until_parked();
18149    cx.set_state(
18150        &r#"
18151        use some::mod1;
18152        use some::mod2;
18153
18154        ˇconst B: u32 = 42;
18155        const C: u32 = 42;
18156
18157
18158        fn main() {
18159            println!("hello");
18160
18161            println!("world");
18162        }
18163        "#
18164        .unindent(),
18165    );
18166
18167    cx.set_head_text(&diff_base);
18168    executor.run_until_parked();
18169
18170    cx.update_editor(|editor, window, cx| {
18171        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18172    });
18173    executor.run_until_parked();
18174
18175    cx.assert_state_with_diff(
18176        r#"
18177        use some::mod1;
18178        use some::mod2;
18179
18180      - const A: u32 = 42;
18181        ˇconst B: u32 = 42;
18182        const C: u32 = 42;
18183
18184
18185        fn main() {
18186            println!("hello");
18187
18188            println!("world");
18189        }
18190      "#
18191        .unindent(),
18192    );
18193
18194    cx.update_editor(|editor, window, cx| {
18195        editor.delete_line(&DeleteLine, window, cx);
18196    });
18197    executor.run_until_parked();
18198    cx.assert_state_with_diff(
18199        r#"
18200        use some::mod1;
18201        use some::mod2;
18202
18203      - const A: u32 = 42;
18204      - const B: u32 = 42;
18205        ˇconst C: u32 = 42;
18206
18207
18208        fn main() {
18209            println!("hello");
18210
18211            println!("world");
18212        }
18213      "#
18214        .unindent(),
18215    );
18216
18217    cx.update_editor(|editor, window, cx| {
18218        editor.delete_line(&DeleteLine, window, cx);
18219    });
18220    executor.run_until_parked();
18221    cx.assert_state_with_diff(
18222        r#"
18223        use some::mod1;
18224        use some::mod2;
18225
18226      - const A: u32 = 42;
18227      - const B: u32 = 42;
18228      - const C: u32 = 42;
18229        ˇ
18230
18231        fn main() {
18232            println!("hello");
18233
18234            println!("world");
18235        }
18236      "#
18237        .unindent(),
18238    );
18239
18240    cx.update_editor(|editor, window, cx| {
18241        editor.handle_input("replacement", window, cx);
18242    });
18243    executor.run_until_parked();
18244    cx.assert_state_with_diff(
18245        r#"
18246        use some::mod1;
18247        use some::mod2;
18248
18249      - const A: u32 = 42;
18250      - const B: u32 = 42;
18251      - const C: u32 = 42;
18252      -
18253      + replacementˇ
18254
18255        fn main() {
18256            println!("hello");
18257
18258            println!("world");
18259        }
18260      "#
18261        .unindent(),
18262    );
18263}
18264
18265#[gpui::test]
18266async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18267    init_test(cx, |_| {});
18268
18269    let mut cx = EditorTestContext::new(cx).await;
18270
18271    let base_text = r#"
18272        one
18273        two
18274        three
18275        four
18276        five
18277    "#
18278    .unindent();
18279    executor.run_until_parked();
18280    cx.set_state(
18281        &r#"
18282        one
18283        two
18284        fˇour
18285        five
18286        "#
18287        .unindent(),
18288    );
18289
18290    cx.set_head_text(&base_text);
18291    executor.run_until_parked();
18292
18293    cx.update_editor(|editor, window, cx| {
18294        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18295    });
18296    executor.run_until_parked();
18297
18298    cx.assert_state_with_diff(
18299        r#"
18300          one
18301          two
18302        - three
18303          fˇour
18304          five
18305        "#
18306        .unindent(),
18307    );
18308
18309    cx.update_editor(|editor, window, cx| {
18310        editor.backspace(&Backspace, window, cx);
18311        editor.backspace(&Backspace, window, cx);
18312    });
18313    executor.run_until_parked();
18314    cx.assert_state_with_diff(
18315        r#"
18316          one
18317          two
18318        - threeˇ
18319        - four
18320        + our
18321          five
18322        "#
18323        .unindent(),
18324    );
18325}
18326
18327#[gpui::test]
18328async fn test_edit_after_expanded_modification_hunk(
18329    executor: BackgroundExecutor,
18330    cx: &mut TestAppContext,
18331) {
18332    init_test(cx, |_| {});
18333
18334    let mut cx = EditorTestContext::new(cx).await;
18335
18336    let diff_base = r#"
18337        use some::mod1;
18338        use some::mod2;
18339
18340        const A: u32 = 42;
18341        const B: u32 = 42;
18342        const C: u32 = 42;
18343        const D: u32 = 42;
18344
18345
18346        fn main() {
18347            println!("hello");
18348
18349            println!("world");
18350        }"#
18351    .unindent();
18352
18353    cx.set_state(
18354        &r#"
18355        use some::mod1;
18356        use some::mod2;
18357
18358        const A: u32 = 42;
18359        const B: u32 = 42;
18360        const C: u32 = 43ˇ
18361        const D: u32 = 42;
18362
18363
18364        fn main() {
18365            println!("hello");
18366
18367            println!("world");
18368        }"#
18369        .unindent(),
18370    );
18371
18372    cx.set_head_text(&diff_base);
18373    executor.run_until_parked();
18374    cx.update_editor(|editor, window, cx| {
18375        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18376    });
18377    executor.run_until_parked();
18378
18379    cx.assert_state_with_diff(
18380        r#"
18381        use some::mod1;
18382        use some::mod2;
18383
18384        const A: u32 = 42;
18385        const B: u32 = 42;
18386      - const C: u32 = 42;
18387      + const C: u32 = 43ˇ
18388        const D: u32 = 42;
18389
18390
18391        fn main() {
18392            println!("hello");
18393
18394            println!("world");
18395        }"#
18396        .unindent(),
18397    );
18398
18399    cx.update_editor(|editor, window, cx| {
18400        editor.handle_input("\nnew_line\n", window, cx);
18401    });
18402    executor.run_until_parked();
18403
18404    cx.assert_state_with_diff(
18405        r#"
18406        use some::mod1;
18407        use some::mod2;
18408
18409        const A: u32 = 42;
18410        const B: u32 = 42;
18411      - const C: u32 = 42;
18412      + const C: u32 = 43
18413      + new_line
18414      + ˇ
18415        const D: u32 = 42;
18416
18417
18418        fn main() {
18419            println!("hello");
18420
18421            println!("world");
18422        }"#
18423        .unindent(),
18424    );
18425}
18426
18427#[gpui::test]
18428async fn test_stage_and_unstage_added_file_hunk(
18429    executor: BackgroundExecutor,
18430    cx: &mut TestAppContext,
18431) {
18432    init_test(cx, |_| {});
18433
18434    let mut cx = EditorTestContext::new(cx).await;
18435    cx.update_editor(|editor, _, cx| {
18436        editor.set_expand_all_diff_hunks(cx);
18437    });
18438
18439    let working_copy = r#"
18440            ˇfn main() {
18441                println!("hello, world!");
18442            }
18443        "#
18444    .unindent();
18445
18446    cx.set_state(&working_copy);
18447    executor.run_until_parked();
18448
18449    cx.assert_state_with_diff(
18450        r#"
18451            + ˇfn main() {
18452            +     println!("hello, world!");
18453            + }
18454        "#
18455        .unindent(),
18456    );
18457    cx.assert_index_text(None);
18458
18459    cx.update_editor(|editor, window, cx| {
18460        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18461    });
18462    executor.run_until_parked();
18463    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18464    cx.assert_state_with_diff(
18465        r#"
18466            + ˇfn main() {
18467            +     println!("hello, world!");
18468            + }
18469        "#
18470        .unindent(),
18471    );
18472
18473    cx.update_editor(|editor, window, cx| {
18474        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18475    });
18476    executor.run_until_parked();
18477    cx.assert_index_text(None);
18478}
18479
18480async fn setup_indent_guides_editor(
18481    text: &str,
18482    cx: &mut TestAppContext,
18483) -> (BufferId, EditorTestContext) {
18484    init_test(cx, |_| {});
18485
18486    let mut cx = EditorTestContext::new(cx).await;
18487
18488    let buffer_id = cx.update_editor(|editor, window, cx| {
18489        editor.set_text(text, window, cx);
18490        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18491
18492        buffer_ids[0]
18493    });
18494
18495    (buffer_id, cx)
18496}
18497
18498fn assert_indent_guides(
18499    range: Range<u32>,
18500    expected: Vec<IndentGuide>,
18501    active_indices: Option<Vec<usize>>,
18502    cx: &mut EditorTestContext,
18503) {
18504    let indent_guides = cx.update_editor(|editor, window, cx| {
18505        let snapshot = editor.snapshot(window, cx).display_snapshot;
18506        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18507            editor,
18508            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18509            true,
18510            &snapshot,
18511            cx,
18512        );
18513
18514        indent_guides.sort_by(|a, b| {
18515            a.depth.cmp(&b.depth).then(
18516                a.start_row
18517                    .cmp(&b.start_row)
18518                    .then(a.end_row.cmp(&b.end_row)),
18519            )
18520        });
18521        indent_guides
18522    });
18523
18524    if let Some(expected) = active_indices {
18525        let active_indices = cx.update_editor(|editor, window, cx| {
18526            let snapshot = editor.snapshot(window, cx).display_snapshot;
18527            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18528        });
18529
18530        assert_eq!(
18531            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18532            expected,
18533            "Active indent guide indices do not match"
18534        );
18535    }
18536
18537    assert_eq!(indent_guides, expected, "Indent guides do not match");
18538}
18539
18540fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18541    IndentGuide {
18542        buffer_id,
18543        start_row: MultiBufferRow(start_row),
18544        end_row: MultiBufferRow(end_row),
18545        depth,
18546        tab_size: 4,
18547        settings: IndentGuideSettings {
18548            enabled: true,
18549            line_width: 1,
18550            active_line_width: 1,
18551            ..Default::default()
18552        },
18553    }
18554}
18555
18556#[gpui::test]
18557async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18558    let (buffer_id, mut cx) = setup_indent_guides_editor(
18559        &"
18560        fn main() {
18561            let a = 1;
18562        }"
18563        .unindent(),
18564        cx,
18565    )
18566    .await;
18567
18568    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18569}
18570
18571#[gpui::test]
18572async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18573    let (buffer_id, mut cx) = setup_indent_guides_editor(
18574        &"
18575        fn main() {
18576            let a = 1;
18577            let b = 2;
18578        }"
18579        .unindent(),
18580        cx,
18581    )
18582    .await;
18583
18584    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18585}
18586
18587#[gpui::test]
18588async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18589    let (buffer_id, mut cx) = setup_indent_guides_editor(
18590        &"
18591        fn main() {
18592            let a = 1;
18593            if a == 3 {
18594                let b = 2;
18595            } else {
18596                let c = 3;
18597            }
18598        }"
18599        .unindent(),
18600        cx,
18601    )
18602    .await;
18603
18604    assert_indent_guides(
18605        0..8,
18606        vec![
18607            indent_guide(buffer_id, 1, 6, 0),
18608            indent_guide(buffer_id, 3, 3, 1),
18609            indent_guide(buffer_id, 5, 5, 1),
18610        ],
18611        None,
18612        &mut cx,
18613    );
18614}
18615
18616#[gpui::test]
18617async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18618    let (buffer_id, mut cx) = setup_indent_guides_editor(
18619        &"
18620        fn main() {
18621            let a = 1;
18622                let b = 2;
18623            let c = 3;
18624        }"
18625        .unindent(),
18626        cx,
18627    )
18628    .await;
18629
18630    assert_indent_guides(
18631        0..5,
18632        vec![
18633            indent_guide(buffer_id, 1, 3, 0),
18634            indent_guide(buffer_id, 2, 2, 1),
18635        ],
18636        None,
18637        &mut cx,
18638    );
18639}
18640
18641#[gpui::test]
18642async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18643    let (buffer_id, mut cx) = setup_indent_guides_editor(
18644        &"
18645        fn main() {
18646            let a = 1;
18647
18648            let c = 3;
18649        }"
18650        .unindent(),
18651        cx,
18652    )
18653    .await;
18654
18655    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18656}
18657
18658#[gpui::test]
18659async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18660    let (buffer_id, mut cx) = setup_indent_guides_editor(
18661        &"
18662        fn main() {
18663            let a = 1;
18664
18665            let c = 3;
18666
18667            if a == 3 {
18668                let b = 2;
18669            } else {
18670                let c = 3;
18671            }
18672        }"
18673        .unindent(),
18674        cx,
18675    )
18676    .await;
18677
18678    assert_indent_guides(
18679        0..11,
18680        vec![
18681            indent_guide(buffer_id, 1, 9, 0),
18682            indent_guide(buffer_id, 6, 6, 1),
18683            indent_guide(buffer_id, 8, 8, 1),
18684        ],
18685        None,
18686        &mut cx,
18687    );
18688}
18689
18690#[gpui::test]
18691async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18692    let (buffer_id, mut cx) = setup_indent_guides_editor(
18693        &"
18694        fn main() {
18695            let a = 1;
18696
18697            let c = 3;
18698
18699            if a == 3 {
18700                let b = 2;
18701            } else {
18702                let c = 3;
18703            }
18704        }"
18705        .unindent(),
18706        cx,
18707    )
18708    .await;
18709
18710    assert_indent_guides(
18711        1..11,
18712        vec![
18713            indent_guide(buffer_id, 1, 9, 0),
18714            indent_guide(buffer_id, 6, 6, 1),
18715            indent_guide(buffer_id, 8, 8, 1),
18716        ],
18717        None,
18718        &mut cx,
18719    );
18720}
18721
18722#[gpui::test]
18723async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18724    let (buffer_id, mut cx) = setup_indent_guides_editor(
18725        &"
18726        fn main() {
18727            let a = 1;
18728
18729            let c = 3;
18730
18731            if a == 3 {
18732                let b = 2;
18733            } else {
18734                let c = 3;
18735            }
18736        }"
18737        .unindent(),
18738        cx,
18739    )
18740    .await;
18741
18742    assert_indent_guides(
18743        1..10,
18744        vec![
18745            indent_guide(buffer_id, 1, 9, 0),
18746            indent_guide(buffer_id, 6, 6, 1),
18747            indent_guide(buffer_id, 8, 8, 1),
18748        ],
18749        None,
18750        &mut cx,
18751    );
18752}
18753
18754#[gpui::test]
18755async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18756    let (buffer_id, mut cx) = setup_indent_guides_editor(
18757        &"
18758        fn main() {
18759            if a {
18760                b(
18761                    c,
18762                    d,
18763                )
18764            } else {
18765                e(
18766                    f
18767                )
18768            }
18769        }"
18770        .unindent(),
18771        cx,
18772    )
18773    .await;
18774
18775    assert_indent_guides(
18776        0..11,
18777        vec![
18778            indent_guide(buffer_id, 1, 10, 0),
18779            indent_guide(buffer_id, 2, 5, 1),
18780            indent_guide(buffer_id, 7, 9, 1),
18781            indent_guide(buffer_id, 3, 4, 2),
18782            indent_guide(buffer_id, 8, 8, 2),
18783        ],
18784        None,
18785        &mut cx,
18786    );
18787
18788    cx.update_editor(|editor, window, cx| {
18789        editor.fold_at(MultiBufferRow(2), window, cx);
18790        assert_eq!(
18791            editor.display_text(cx),
18792            "
18793            fn main() {
18794                if a {
18795                    b(⋯
18796                    )
18797                } else {
18798                    e(
18799                        f
18800                    )
18801                }
18802            }"
18803            .unindent()
18804        );
18805    });
18806
18807    assert_indent_guides(
18808        0..11,
18809        vec![
18810            indent_guide(buffer_id, 1, 10, 0),
18811            indent_guide(buffer_id, 2, 5, 1),
18812            indent_guide(buffer_id, 7, 9, 1),
18813            indent_guide(buffer_id, 8, 8, 2),
18814        ],
18815        None,
18816        &mut cx,
18817    );
18818}
18819
18820#[gpui::test]
18821async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18822    let (buffer_id, mut cx) = setup_indent_guides_editor(
18823        &"
18824        block1
18825            block2
18826                block3
18827                    block4
18828            block2
18829        block1
18830        block1"
18831            .unindent(),
18832        cx,
18833    )
18834    .await;
18835
18836    assert_indent_guides(
18837        1..10,
18838        vec![
18839            indent_guide(buffer_id, 1, 4, 0),
18840            indent_guide(buffer_id, 2, 3, 1),
18841            indent_guide(buffer_id, 3, 3, 2),
18842        ],
18843        None,
18844        &mut cx,
18845    );
18846}
18847
18848#[gpui::test]
18849async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18850    let (buffer_id, mut cx) = setup_indent_guides_editor(
18851        &"
18852        block1
18853            block2
18854                block3
18855
18856        block1
18857        block1"
18858            .unindent(),
18859        cx,
18860    )
18861    .await;
18862
18863    assert_indent_guides(
18864        0..6,
18865        vec![
18866            indent_guide(buffer_id, 1, 2, 0),
18867            indent_guide(buffer_id, 2, 2, 1),
18868        ],
18869        None,
18870        &mut cx,
18871    );
18872}
18873
18874#[gpui::test]
18875async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18876    let (buffer_id, mut cx) = setup_indent_guides_editor(
18877        &"
18878        function component() {
18879        \treturn (
18880        \t\t\t
18881        \t\t<div>
18882        \t\t\t<abc></abc>
18883        \t\t</div>
18884        \t)
18885        }"
18886        .unindent(),
18887        cx,
18888    )
18889    .await;
18890
18891    assert_indent_guides(
18892        0..8,
18893        vec![
18894            indent_guide(buffer_id, 1, 6, 0),
18895            indent_guide(buffer_id, 2, 5, 1),
18896            indent_guide(buffer_id, 4, 4, 2),
18897        ],
18898        None,
18899        &mut cx,
18900    );
18901}
18902
18903#[gpui::test]
18904async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18905    let (buffer_id, mut cx) = setup_indent_guides_editor(
18906        &"
18907        function component() {
18908        \treturn (
18909        \t
18910        \t\t<div>
18911        \t\t\t<abc></abc>
18912        \t\t</div>
18913        \t)
18914        }"
18915        .unindent(),
18916        cx,
18917    )
18918    .await;
18919
18920    assert_indent_guides(
18921        0..8,
18922        vec![
18923            indent_guide(buffer_id, 1, 6, 0),
18924            indent_guide(buffer_id, 2, 5, 1),
18925            indent_guide(buffer_id, 4, 4, 2),
18926        ],
18927        None,
18928        &mut cx,
18929    );
18930}
18931
18932#[gpui::test]
18933async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18934    let (buffer_id, mut cx) = setup_indent_guides_editor(
18935        &"
18936        block1
18937
18938
18939
18940            block2
18941        "
18942        .unindent(),
18943        cx,
18944    )
18945    .await;
18946
18947    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18948}
18949
18950#[gpui::test]
18951async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18952    let (buffer_id, mut cx) = setup_indent_guides_editor(
18953        &"
18954        def a:
18955        \tb = 3
18956        \tif True:
18957        \t\tc = 4
18958        \t\td = 5
18959        \tprint(b)
18960        "
18961        .unindent(),
18962        cx,
18963    )
18964    .await;
18965
18966    assert_indent_guides(
18967        0..6,
18968        vec![
18969            indent_guide(buffer_id, 1, 5, 0),
18970            indent_guide(buffer_id, 3, 4, 1),
18971        ],
18972        None,
18973        &mut cx,
18974    );
18975}
18976
18977#[gpui::test]
18978async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18979    let (buffer_id, mut cx) = setup_indent_guides_editor(
18980        &"
18981    fn main() {
18982        let a = 1;
18983    }"
18984        .unindent(),
18985        cx,
18986    )
18987    .await;
18988
18989    cx.update_editor(|editor, window, cx| {
18990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18991            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18992        });
18993    });
18994
18995    assert_indent_guides(
18996        0..3,
18997        vec![indent_guide(buffer_id, 1, 1, 0)],
18998        Some(vec![0]),
18999        &mut cx,
19000    );
19001}
19002
19003#[gpui::test]
19004async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19005    let (buffer_id, mut cx) = setup_indent_guides_editor(
19006        &"
19007    fn main() {
19008        if 1 == 2 {
19009            let a = 1;
19010        }
19011    }"
19012        .unindent(),
19013        cx,
19014    )
19015    .await;
19016
19017    cx.update_editor(|editor, window, cx| {
19018        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19019            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19020        });
19021    });
19022
19023    assert_indent_guides(
19024        0..4,
19025        vec![
19026            indent_guide(buffer_id, 1, 3, 0),
19027            indent_guide(buffer_id, 2, 2, 1),
19028        ],
19029        Some(vec![1]),
19030        &mut cx,
19031    );
19032
19033    cx.update_editor(|editor, window, cx| {
19034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19035            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19036        });
19037    });
19038
19039    assert_indent_guides(
19040        0..4,
19041        vec![
19042            indent_guide(buffer_id, 1, 3, 0),
19043            indent_guide(buffer_id, 2, 2, 1),
19044        ],
19045        Some(vec![1]),
19046        &mut cx,
19047    );
19048
19049    cx.update_editor(|editor, window, cx| {
19050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19051            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19052        });
19053    });
19054
19055    assert_indent_guides(
19056        0..4,
19057        vec![
19058            indent_guide(buffer_id, 1, 3, 0),
19059            indent_guide(buffer_id, 2, 2, 1),
19060        ],
19061        Some(vec![0]),
19062        &mut cx,
19063    );
19064}
19065
19066#[gpui::test]
19067async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19068    let (buffer_id, mut cx) = setup_indent_guides_editor(
19069        &"
19070    fn main() {
19071        let a = 1;
19072
19073        let b = 2;
19074    }"
19075        .unindent(),
19076        cx,
19077    )
19078    .await;
19079
19080    cx.update_editor(|editor, window, cx| {
19081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19082            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19083        });
19084    });
19085
19086    assert_indent_guides(
19087        0..5,
19088        vec![indent_guide(buffer_id, 1, 3, 0)],
19089        Some(vec![0]),
19090        &mut cx,
19091    );
19092}
19093
19094#[gpui::test]
19095async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19096    let (buffer_id, mut cx) = setup_indent_guides_editor(
19097        &"
19098    def m:
19099        a = 1
19100        pass"
19101            .unindent(),
19102        cx,
19103    )
19104    .await;
19105
19106    cx.update_editor(|editor, window, cx| {
19107        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19108            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19109        });
19110    });
19111
19112    assert_indent_guides(
19113        0..3,
19114        vec![indent_guide(buffer_id, 1, 2, 0)],
19115        Some(vec![0]),
19116        &mut cx,
19117    );
19118}
19119
19120#[gpui::test]
19121async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19122    init_test(cx, |_| {});
19123    let mut cx = EditorTestContext::new(cx).await;
19124    let text = indoc! {
19125        "
19126        impl A {
19127            fn b() {
19128                0;
19129                3;
19130                5;
19131                6;
19132                7;
19133            }
19134        }
19135        "
19136    };
19137    let base_text = indoc! {
19138        "
19139        impl A {
19140            fn b() {
19141                0;
19142                1;
19143                2;
19144                3;
19145                4;
19146            }
19147            fn c() {
19148                5;
19149                6;
19150                7;
19151            }
19152        }
19153        "
19154    };
19155
19156    cx.update_editor(|editor, window, cx| {
19157        editor.set_text(text, window, cx);
19158
19159        editor.buffer().update(cx, |multibuffer, cx| {
19160            let buffer = multibuffer.as_singleton().unwrap();
19161            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19162
19163            multibuffer.set_all_diff_hunks_expanded(cx);
19164            multibuffer.add_diff(diff, cx);
19165
19166            buffer.read(cx).remote_id()
19167        })
19168    });
19169    cx.run_until_parked();
19170
19171    cx.assert_state_with_diff(
19172        indoc! { "
19173          impl A {
19174              fn b() {
19175                  0;
19176        -         1;
19177        -         2;
19178                  3;
19179        -         4;
19180        -     }
19181        -     fn c() {
19182                  5;
19183                  6;
19184                  7;
19185              }
19186          }
19187          ˇ"
19188        }
19189        .to_string(),
19190    );
19191
19192    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19193        editor
19194            .snapshot(window, cx)
19195            .buffer_snapshot
19196            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19197            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19198            .collect::<Vec<_>>()
19199    });
19200    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19201    assert_eq!(
19202        actual_guides,
19203        vec![
19204            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19205            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19206            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19207        ]
19208    );
19209}
19210
19211#[gpui::test]
19212async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19213    init_test(cx, |_| {});
19214    let mut cx = EditorTestContext::new(cx).await;
19215
19216    let diff_base = r#"
19217        a
19218        b
19219        c
19220        "#
19221    .unindent();
19222
19223    cx.set_state(
19224        &r#"
19225        ˇA
19226        b
19227        C
19228        "#
19229        .unindent(),
19230    );
19231    cx.set_head_text(&diff_base);
19232    cx.update_editor(|editor, window, cx| {
19233        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19234    });
19235    executor.run_until_parked();
19236
19237    let both_hunks_expanded = r#"
19238        - a
19239        + ˇA
19240          b
19241        - c
19242        + C
19243        "#
19244    .unindent();
19245
19246    cx.assert_state_with_diff(both_hunks_expanded.clone());
19247
19248    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19249        let snapshot = editor.snapshot(window, cx);
19250        let hunks = editor
19251            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19252            .collect::<Vec<_>>();
19253        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19254        let buffer_id = hunks[0].buffer_id;
19255        hunks
19256            .into_iter()
19257            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19258            .collect::<Vec<_>>()
19259    });
19260    assert_eq!(hunk_ranges.len(), 2);
19261
19262    cx.update_editor(|editor, _, cx| {
19263        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19264    });
19265    executor.run_until_parked();
19266
19267    let second_hunk_expanded = r#"
19268          ˇA
19269          b
19270        - c
19271        + C
19272        "#
19273    .unindent();
19274
19275    cx.assert_state_with_diff(second_hunk_expanded);
19276
19277    cx.update_editor(|editor, _, cx| {
19278        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19279    });
19280    executor.run_until_parked();
19281
19282    cx.assert_state_with_diff(both_hunks_expanded.clone());
19283
19284    cx.update_editor(|editor, _, cx| {
19285        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19286    });
19287    executor.run_until_parked();
19288
19289    let first_hunk_expanded = r#"
19290        - a
19291        + ˇA
19292          b
19293          C
19294        "#
19295    .unindent();
19296
19297    cx.assert_state_with_diff(first_hunk_expanded);
19298
19299    cx.update_editor(|editor, _, cx| {
19300        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19301    });
19302    executor.run_until_parked();
19303
19304    cx.assert_state_with_diff(both_hunks_expanded);
19305
19306    cx.set_state(
19307        &r#"
19308        ˇA
19309        b
19310        "#
19311        .unindent(),
19312    );
19313    cx.run_until_parked();
19314
19315    // TODO this cursor position seems bad
19316    cx.assert_state_with_diff(
19317        r#"
19318        - ˇa
19319        + A
19320          b
19321        "#
19322        .unindent(),
19323    );
19324
19325    cx.update_editor(|editor, window, cx| {
19326        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19327    });
19328
19329    cx.assert_state_with_diff(
19330        r#"
19331            - ˇa
19332            + A
19333              b
19334            - c
19335            "#
19336        .unindent(),
19337    );
19338
19339    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19340        let snapshot = editor.snapshot(window, cx);
19341        let hunks = editor
19342            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19343            .collect::<Vec<_>>();
19344        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19345        let buffer_id = hunks[0].buffer_id;
19346        hunks
19347            .into_iter()
19348            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19349            .collect::<Vec<_>>()
19350    });
19351    assert_eq!(hunk_ranges.len(), 2);
19352
19353    cx.update_editor(|editor, _, cx| {
19354        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19355    });
19356    executor.run_until_parked();
19357
19358    cx.assert_state_with_diff(
19359        r#"
19360        - ˇa
19361        + A
19362          b
19363        "#
19364        .unindent(),
19365    );
19366}
19367
19368#[gpui::test]
19369async fn test_toggle_deletion_hunk_at_start_of_file(
19370    executor: BackgroundExecutor,
19371    cx: &mut TestAppContext,
19372) {
19373    init_test(cx, |_| {});
19374    let mut cx = EditorTestContext::new(cx).await;
19375
19376    let diff_base = r#"
19377        a
19378        b
19379        c
19380        "#
19381    .unindent();
19382
19383    cx.set_state(
19384        &r#"
19385        ˇb
19386        c
19387        "#
19388        .unindent(),
19389    );
19390    cx.set_head_text(&diff_base);
19391    cx.update_editor(|editor, window, cx| {
19392        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19393    });
19394    executor.run_until_parked();
19395
19396    let hunk_expanded = r#"
19397        - a
19398          ˇb
19399          c
19400        "#
19401    .unindent();
19402
19403    cx.assert_state_with_diff(hunk_expanded.clone());
19404
19405    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19406        let snapshot = editor.snapshot(window, cx);
19407        let hunks = editor
19408            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19409            .collect::<Vec<_>>();
19410        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19411        let buffer_id = hunks[0].buffer_id;
19412        hunks
19413            .into_iter()
19414            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19415            .collect::<Vec<_>>()
19416    });
19417    assert_eq!(hunk_ranges.len(), 1);
19418
19419    cx.update_editor(|editor, _, cx| {
19420        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19421    });
19422    executor.run_until_parked();
19423
19424    let hunk_collapsed = r#"
19425          ˇb
19426          c
19427        "#
19428    .unindent();
19429
19430    cx.assert_state_with_diff(hunk_collapsed);
19431
19432    cx.update_editor(|editor, _, cx| {
19433        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19434    });
19435    executor.run_until_parked();
19436
19437    cx.assert_state_with_diff(hunk_expanded.clone());
19438}
19439
19440#[gpui::test]
19441async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19442    init_test(cx, |_| {});
19443
19444    let fs = FakeFs::new(cx.executor());
19445    fs.insert_tree(
19446        path!("/test"),
19447        json!({
19448            ".git": {},
19449            "file-1": "ONE\n",
19450            "file-2": "TWO\n",
19451            "file-3": "THREE\n",
19452        }),
19453    )
19454    .await;
19455
19456    fs.set_head_for_repo(
19457        path!("/test/.git").as_ref(),
19458        &[
19459            ("file-1".into(), "one\n".into()),
19460            ("file-2".into(), "two\n".into()),
19461            ("file-3".into(), "three\n".into()),
19462        ],
19463        "deadbeef",
19464    );
19465
19466    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19467    let mut buffers = vec![];
19468    for i in 1..=3 {
19469        let buffer = project
19470            .update(cx, |project, cx| {
19471                let path = format!(path!("/test/file-{}"), i);
19472                project.open_local_buffer(path, cx)
19473            })
19474            .await
19475            .unwrap();
19476        buffers.push(buffer);
19477    }
19478
19479    let multibuffer = cx.new(|cx| {
19480        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19481        multibuffer.set_all_diff_hunks_expanded(cx);
19482        for buffer in &buffers {
19483            let snapshot = buffer.read(cx).snapshot();
19484            multibuffer.set_excerpts_for_path(
19485                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19486                buffer.clone(),
19487                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19488                DEFAULT_MULTIBUFFER_CONTEXT,
19489                cx,
19490            );
19491        }
19492        multibuffer
19493    });
19494
19495    let editor = cx.add_window(|window, cx| {
19496        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19497    });
19498    cx.run_until_parked();
19499
19500    let snapshot = editor
19501        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19502        .unwrap();
19503    let hunks = snapshot
19504        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19505        .map(|hunk| match hunk {
19506            DisplayDiffHunk::Unfolded {
19507                display_row_range, ..
19508            } => display_row_range,
19509            DisplayDiffHunk::Folded { .. } => unreachable!(),
19510        })
19511        .collect::<Vec<_>>();
19512    assert_eq!(
19513        hunks,
19514        [
19515            DisplayRow(2)..DisplayRow(4),
19516            DisplayRow(7)..DisplayRow(9),
19517            DisplayRow(12)..DisplayRow(14),
19518        ]
19519    );
19520}
19521
19522#[gpui::test]
19523async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19524    init_test(cx, |_| {});
19525
19526    let mut cx = EditorTestContext::new(cx).await;
19527    cx.set_head_text(indoc! { "
19528        one
19529        two
19530        three
19531        four
19532        five
19533        "
19534    });
19535    cx.set_index_text(indoc! { "
19536        one
19537        two
19538        three
19539        four
19540        five
19541        "
19542    });
19543    cx.set_state(indoc! {"
19544        one
19545        TWO
19546        ˇTHREE
19547        FOUR
19548        five
19549    "});
19550    cx.run_until_parked();
19551    cx.update_editor(|editor, window, cx| {
19552        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19553    });
19554    cx.run_until_parked();
19555    cx.assert_index_text(Some(indoc! {"
19556        one
19557        TWO
19558        THREE
19559        FOUR
19560        five
19561    "}));
19562    cx.set_state(indoc! { "
19563        one
19564        TWO
19565        ˇTHREE-HUNDRED
19566        FOUR
19567        five
19568    "});
19569    cx.run_until_parked();
19570    cx.update_editor(|editor, window, cx| {
19571        let snapshot = editor.snapshot(window, cx);
19572        let hunks = editor
19573            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19574            .collect::<Vec<_>>();
19575        assert_eq!(hunks.len(), 1);
19576        assert_eq!(
19577            hunks[0].status(),
19578            DiffHunkStatus {
19579                kind: DiffHunkStatusKind::Modified,
19580                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19581            }
19582        );
19583
19584        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19585    });
19586    cx.run_until_parked();
19587    cx.assert_index_text(Some(indoc! {"
19588        one
19589        TWO
19590        THREE-HUNDRED
19591        FOUR
19592        five
19593    "}));
19594}
19595
19596#[gpui::test]
19597fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19598    init_test(cx, |_| {});
19599
19600    let editor = cx.add_window(|window, cx| {
19601        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19602        build_editor(buffer, window, cx)
19603    });
19604
19605    let render_args = Arc::new(Mutex::new(None));
19606    let snapshot = editor
19607        .update(cx, |editor, window, cx| {
19608            let snapshot = editor.buffer().read(cx).snapshot(cx);
19609            let range =
19610                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19611
19612            struct RenderArgs {
19613                row: MultiBufferRow,
19614                folded: bool,
19615                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19616            }
19617
19618            let crease = Crease::inline(
19619                range,
19620                FoldPlaceholder::test(),
19621                {
19622                    let toggle_callback = render_args.clone();
19623                    move |row, folded, callback, _window, _cx| {
19624                        *toggle_callback.lock() = Some(RenderArgs {
19625                            row,
19626                            folded,
19627                            callback,
19628                        });
19629                        div()
19630                    }
19631                },
19632                |_row, _folded, _window, _cx| div(),
19633            );
19634
19635            editor.insert_creases(Some(crease), cx);
19636            let snapshot = editor.snapshot(window, cx);
19637            let _div = snapshot.render_crease_toggle(
19638                MultiBufferRow(1),
19639                false,
19640                cx.entity().clone(),
19641                window,
19642                cx,
19643            );
19644            snapshot
19645        })
19646        .unwrap();
19647
19648    let render_args = render_args.lock().take().unwrap();
19649    assert_eq!(render_args.row, MultiBufferRow(1));
19650    assert!(!render_args.folded);
19651    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19652
19653    cx.update_window(*editor, |_, window, cx| {
19654        (render_args.callback)(true, window, cx)
19655    })
19656    .unwrap();
19657    let snapshot = editor
19658        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19659        .unwrap();
19660    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19661
19662    cx.update_window(*editor, |_, window, cx| {
19663        (render_args.callback)(false, window, cx)
19664    })
19665    .unwrap();
19666    let snapshot = editor
19667        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19668        .unwrap();
19669    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19670}
19671
19672#[gpui::test]
19673async fn test_input_text(cx: &mut TestAppContext) {
19674    init_test(cx, |_| {});
19675    let mut cx = EditorTestContext::new(cx).await;
19676
19677    cx.set_state(
19678        &r#"ˇone
19679        two
19680
19681        three
19682        fourˇ
19683        five
19684
19685        siˇx"#
19686            .unindent(),
19687    );
19688
19689    cx.dispatch_action(HandleInput(String::new()));
19690    cx.assert_editor_state(
19691        &r#"ˇone
19692        two
19693
19694        three
19695        fourˇ
19696        five
19697
19698        siˇx"#
19699            .unindent(),
19700    );
19701
19702    cx.dispatch_action(HandleInput("AAAA".to_string()));
19703    cx.assert_editor_state(
19704        &r#"AAAAˇone
19705        two
19706
19707        three
19708        fourAAAAˇ
19709        five
19710
19711        siAAAAˇx"#
19712            .unindent(),
19713    );
19714}
19715
19716#[gpui::test]
19717async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19718    init_test(cx, |_| {});
19719
19720    let mut cx = EditorTestContext::new(cx).await;
19721    cx.set_state(
19722        r#"let foo = 1;
19723let foo = 2;
19724let foo = 3;
19725let fooˇ = 4;
19726let foo = 5;
19727let foo = 6;
19728let foo = 7;
19729let foo = 8;
19730let foo = 9;
19731let foo = 10;
19732let foo = 11;
19733let foo = 12;
19734let foo = 13;
19735let foo = 14;
19736let foo = 15;"#,
19737    );
19738
19739    cx.update_editor(|e, window, cx| {
19740        assert_eq!(
19741            e.next_scroll_position,
19742            NextScrollCursorCenterTopBottom::Center,
19743            "Default next scroll direction is center",
19744        );
19745
19746        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19747        assert_eq!(
19748            e.next_scroll_position,
19749            NextScrollCursorCenterTopBottom::Top,
19750            "After center, next scroll direction should be top",
19751        );
19752
19753        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19754        assert_eq!(
19755            e.next_scroll_position,
19756            NextScrollCursorCenterTopBottom::Bottom,
19757            "After top, next scroll direction should be bottom",
19758        );
19759
19760        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19761        assert_eq!(
19762            e.next_scroll_position,
19763            NextScrollCursorCenterTopBottom::Center,
19764            "After bottom, scrolling should start over",
19765        );
19766
19767        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19768        assert_eq!(
19769            e.next_scroll_position,
19770            NextScrollCursorCenterTopBottom::Top,
19771            "Scrolling continues if retriggered fast enough"
19772        );
19773    });
19774
19775    cx.executor()
19776        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19777    cx.executor().run_until_parked();
19778    cx.update_editor(|e, _, _| {
19779        assert_eq!(
19780            e.next_scroll_position,
19781            NextScrollCursorCenterTopBottom::Center,
19782            "If scrolling is not triggered fast enough, it should reset"
19783        );
19784    });
19785}
19786
19787#[gpui::test]
19788async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19789    init_test(cx, |_| {});
19790    let mut cx = EditorLspTestContext::new_rust(
19791        lsp::ServerCapabilities {
19792            definition_provider: Some(lsp::OneOf::Left(true)),
19793            references_provider: Some(lsp::OneOf::Left(true)),
19794            ..lsp::ServerCapabilities::default()
19795        },
19796        cx,
19797    )
19798    .await;
19799
19800    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19801        let go_to_definition = cx
19802            .lsp
19803            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19804                move |params, _| async move {
19805                    if empty_go_to_definition {
19806                        Ok(None)
19807                    } else {
19808                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19809                            uri: params.text_document_position_params.text_document.uri,
19810                            range: lsp::Range::new(
19811                                lsp::Position::new(4, 3),
19812                                lsp::Position::new(4, 6),
19813                            ),
19814                        })))
19815                    }
19816                },
19817            );
19818        let references = cx
19819            .lsp
19820            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19821                Ok(Some(vec![lsp::Location {
19822                    uri: params.text_document_position.text_document.uri,
19823                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19824                }]))
19825            });
19826        (go_to_definition, references)
19827    };
19828
19829    cx.set_state(
19830        &r#"fn one() {
19831            let mut a = ˇtwo();
19832        }
19833
19834        fn two() {}"#
19835            .unindent(),
19836    );
19837    set_up_lsp_handlers(false, &mut cx);
19838    let navigated = cx
19839        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19840        .await
19841        .expect("Failed to navigate to definition");
19842    assert_eq!(
19843        navigated,
19844        Navigated::Yes,
19845        "Should have navigated to definition from the GetDefinition response"
19846    );
19847    cx.assert_editor_state(
19848        &r#"fn one() {
19849            let mut a = two();
19850        }
19851
19852        fn «twoˇ»() {}"#
19853            .unindent(),
19854    );
19855
19856    let editors = cx.update_workspace(|workspace, _, cx| {
19857        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19858    });
19859    cx.update_editor(|_, _, test_editor_cx| {
19860        assert_eq!(
19861            editors.len(),
19862            1,
19863            "Initially, only one, test, editor should be open in the workspace"
19864        );
19865        assert_eq!(
19866            test_editor_cx.entity(),
19867            editors.last().expect("Asserted len is 1").clone()
19868        );
19869    });
19870
19871    set_up_lsp_handlers(true, &mut cx);
19872    let navigated = cx
19873        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19874        .await
19875        .expect("Failed to navigate to lookup references");
19876    assert_eq!(
19877        navigated,
19878        Navigated::Yes,
19879        "Should have navigated to references as a fallback after empty GoToDefinition response"
19880    );
19881    // We should not change the selections in the existing file,
19882    // if opening another milti buffer with the references
19883    cx.assert_editor_state(
19884        &r#"fn one() {
19885            let mut a = two();
19886        }
19887
19888        fn «twoˇ»() {}"#
19889            .unindent(),
19890    );
19891    let editors = cx.update_workspace(|workspace, _, cx| {
19892        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19893    });
19894    cx.update_editor(|_, _, test_editor_cx| {
19895        assert_eq!(
19896            editors.len(),
19897            2,
19898            "After falling back to references search, we open a new editor with the results"
19899        );
19900        let references_fallback_text = editors
19901            .into_iter()
19902            .find(|new_editor| *new_editor != test_editor_cx.entity())
19903            .expect("Should have one non-test editor now")
19904            .read(test_editor_cx)
19905            .text(test_editor_cx);
19906        assert_eq!(
19907            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19908            "Should use the range from the references response and not the GoToDefinition one"
19909        );
19910    });
19911}
19912
19913#[gpui::test]
19914async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19915    init_test(cx, |_| {});
19916    cx.update(|cx| {
19917        let mut editor_settings = EditorSettings::get_global(cx).clone();
19918        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19919        EditorSettings::override_global(editor_settings, cx);
19920    });
19921    let mut cx = EditorLspTestContext::new_rust(
19922        lsp::ServerCapabilities {
19923            definition_provider: Some(lsp::OneOf::Left(true)),
19924            references_provider: Some(lsp::OneOf::Left(true)),
19925            ..lsp::ServerCapabilities::default()
19926        },
19927        cx,
19928    )
19929    .await;
19930    let original_state = r#"fn one() {
19931        let mut a = ˇtwo();
19932    }
19933
19934    fn two() {}"#
19935        .unindent();
19936    cx.set_state(&original_state);
19937
19938    let mut go_to_definition = cx
19939        .lsp
19940        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19941            move |_, _| async move { Ok(None) },
19942        );
19943    let _references = cx
19944        .lsp
19945        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19946            panic!("Should not call for references with no go to definition fallback")
19947        });
19948
19949    let navigated = cx
19950        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19951        .await
19952        .expect("Failed to navigate to lookup references");
19953    go_to_definition
19954        .next()
19955        .await
19956        .expect("Should have called the go_to_definition handler");
19957
19958    assert_eq!(
19959        navigated,
19960        Navigated::No,
19961        "Should have navigated to references as a fallback after empty GoToDefinition response"
19962    );
19963    cx.assert_editor_state(&original_state);
19964    let editors = cx.update_workspace(|workspace, _, cx| {
19965        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19966    });
19967    cx.update_editor(|_, _, _| {
19968        assert_eq!(
19969            editors.len(),
19970            1,
19971            "After unsuccessful fallback, no other editor should have been opened"
19972        );
19973    });
19974}
19975
19976#[gpui::test]
19977async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19978    init_test(cx, |_| {});
19979
19980    let language = Arc::new(Language::new(
19981        LanguageConfig::default(),
19982        Some(tree_sitter_rust::LANGUAGE.into()),
19983    ));
19984
19985    let text = r#"
19986        #[cfg(test)]
19987        mod tests() {
19988            #[test]
19989            fn runnable_1() {
19990                let a = 1;
19991            }
19992
19993            #[test]
19994            fn runnable_2() {
19995                let a = 1;
19996                let b = 2;
19997            }
19998        }
19999    "#
20000    .unindent();
20001
20002    let fs = FakeFs::new(cx.executor());
20003    fs.insert_file("/file.rs", Default::default()).await;
20004
20005    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20006    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20007    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20008    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20009    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20010
20011    let editor = cx.new_window_entity(|window, cx| {
20012        Editor::new(
20013            EditorMode::full(),
20014            multi_buffer,
20015            Some(project.clone()),
20016            window,
20017            cx,
20018        )
20019    });
20020
20021    editor.update_in(cx, |editor, window, cx| {
20022        let snapshot = editor.buffer().read(cx).snapshot(cx);
20023        editor.tasks.insert(
20024            (buffer.read(cx).remote_id(), 3),
20025            RunnableTasks {
20026                templates: vec![],
20027                offset: snapshot.anchor_before(43),
20028                column: 0,
20029                extra_variables: HashMap::default(),
20030                context_range: BufferOffset(43)..BufferOffset(85),
20031            },
20032        );
20033        editor.tasks.insert(
20034            (buffer.read(cx).remote_id(), 8),
20035            RunnableTasks {
20036                templates: vec![],
20037                offset: snapshot.anchor_before(86),
20038                column: 0,
20039                extra_variables: HashMap::default(),
20040                context_range: BufferOffset(86)..BufferOffset(191),
20041            },
20042        );
20043
20044        // Test finding task when cursor is inside function body
20045        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20046            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20047        });
20048        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20049        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20050
20051        // Test finding task when cursor is on function name
20052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20053            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20054        });
20055        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20056        assert_eq!(row, 8, "Should find task when cursor is on function name");
20057    });
20058}
20059
20060#[gpui::test]
20061async fn test_folding_buffers(cx: &mut TestAppContext) {
20062    init_test(cx, |_| {});
20063
20064    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20065    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20066    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20067
20068    let fs = FakeFs::new(cx.executor());
20069    fs.insert_tree(
20070        path!("/a"),
20071        json!({
20072            "first.rs": sample_text_1,
20073            "second.rs": sample_text_2,
20074            "third.rs": sample_text_3,
20075        }),
20076    )
20077    .await;
20078    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20079    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20080    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20081    let worktree = project.update(cx, |project, cx| {
20082        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20083        assert_eq!(worktrees.len(), 1);
20084        worktrees.pop().unwrap()
20085    });
20086    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20087
20088    let buffer_1 = project
20089        .update(cx, |project, cx| {
20090            project.open_buffer((worktree_id, "first.rs"), cx)
20091        })
20092        .await
20093        .unwrap();
20094    let buffer_2 = project
20095        .update(cx, |project, cx| {
20096            project.open_buffer((worktree_id, "second.rs"), cx)
20097        })
20098        .await
20099        .unwrap();
20100    let buffer_3 = project
20101        .update(cx, |project, cx| {
20102            project.open_buffer((worktree_id, "third.rs"), cx)
20103        })
20104        .await
20105        .unwrap();
20106
20107    let multi_buffer = cx.new(|cx| {
20108        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20109        multi_buffer.push_excerpts(
20110            buffer_1.clone(),
20111            [
20112                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20113                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20114                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20115            ],
20116            cx,
20117        );
20118        multi_buffer.push_excerpts(
20119            buffer_2.clone(),
20120            [
20121                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20122                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20123                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20124            ],
20125            cx,
20126        );
20127        multi_buffer.push_excerpts(
20128            buffer_3.clone(),
20129            [
20130                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20131                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20132                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20133            ],
20134            cx,
20135        );
20136        multi_buffer
20137    });
20138    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20139        Editor::new(
20140            EditorMode::full(),
20141            multi_buffer.clone(),
20142            Some(project.clone()),
20143            window,
20144            cx,
20145        )
20146    });
20147
20148    assert_eq!(
20149        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20150        "\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",
20151    );
20152
20153    multi_buffer_editor.update(cx, |editor, cx| {
20154        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20155    });
20156    assert_eq!(
20157        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20158        "\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",
20159        "After folding the first buffer, its text should not be displayed"
20160    );
20161
20162    multi_buffer_editor.update(cx, |editor, cx| {
20163        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20164    });
20165    assert_eq!(
20166        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20167        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20168        "After folding the second buffer, its text should not be displayed"
20169    );
20170
20171    multi_buffer_editor.update(cx, |editor, cx| {
20172        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20173    });
20174    assert_eq!(
20175        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20176        "\n\n\n\n\n",
20177        "After folding the third buffer, its text should not be displayed"
20178    );
20179
20180    // Emulate selection inside the fold logic, that should work
20181    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20182        editor
20183            .snapshot(window, cx)
20184            .next_line_boundary(Point::new(0, 4));
20185    });
20186
20187    multi_buffer_editor.update(cx, |editor, cx| {
20188        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20189    });
20190    assert_eq!(
20191        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20192        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20193        "After unfolding the second buffer, its text should be displayed"
20194    );
20195
20196    // Typing inside of buffer 1 causes that buffer to be unfolded.
20197    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20198        assert_eq!(
20199            multi_buffer
20200                .read(cx)
20201                .snapshot(cx)
20202                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20203                .collect::<String>(),
20204            "bbbb"
20205        );
20206        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20207            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20208        });
20209        editor.handle_input("B", window, cx);
20210    });
20211
20212    assert_eq!(
20213        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20214        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20215        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20216    );
20217
20218    multi_buffer_editor.update(cx, |editor, cx| {
20219        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20220    });
20221    assert_eq!(
20222        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20223        "\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",
20224        "After unfolding the all buffers, all original text should be displayed"
20225    );
20226}
20227
20228#[gpui::test]
20229async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20230    init_test(cx, |_| {});
20231
20232    let sample_text_1 = "1111\n2222\n3333".to_string();
20233    let sample_text_2 = "4444\n5555\n6666".to_string();
20234    let sample_text_3 = "7777\n8888\n9999".to_string();
20235
20236    let fs = FakeFs::new(cx.executor());
20237    fs.insert_tree(
20238        path!("/a"),
20239        json!({
20240            "first.rs": sample_text_1,
20241            "second.rs": sample_text_2,
20242            "third.rs": sample_text_3,
20243        }),
20244    )
20245    .await;
20246    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20247    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20248    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20249    let worktree = project.update(cx, |project, cx| {
20250        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20251        assert_eq!(worktrees.len(), 1);
20252        worktrees.pop().unwrap()
20253    });
20254    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20255
20256    let buffer_1 = project
20257        .update(cx, |project, cx| {
20258            project.open_buffer((worktree_id, "first.rs"), cx)
20259        })
20260        .await
20261        .unwrap();
20262    let buffer_2 = project
20263        .update(cx, |project, cx| {
20264            project.open_buffer((worktree_id, "second.rs"), cx)
20265        })
20266        .await
20267        .unwrap();
20268    let buffer_3 = project
20269        .update(cx, |project, cx| {
20270            project.open_buffer((worktree_id, "third.rs"), cx)
20271        })
20272        .await
20273        .unwrap();
20274
20275    let multi_buffer = cx.new(|cx| {
20276        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20277        multi_buffer.push_excerpts(
20278            buffer_1.clone(),
20279            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20280            cx,
20281        );
20282        multi_buffer.push_excerpts(
20283            buffer_2.clone(),
20284            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20285            cx,
20286        );
20287        multi_buffer.push_excerpts(
20288            buffer_3.clone(),
20289            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20290            cx,
20291        );
20292        multi_buffer
20293    });
20294
20295    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20296        Editor::new(
20297            EditorMode::full(),
20298            multi_buffer,
20299            Some(project.clone()),
20300            window,
20301            cx,
20302        )
20303    });
20304
20305    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20306    assert_eq!(
20307        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20308        full_text,
20309    );
20310
20311    multi_buffer_editor.update(cx, |editor, cx| {
20312        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20313    });
20314    assert_eq!(
20315        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20316        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20317        "After folding the first buffer, its text should not be displayed"
20318    );
20319
20320    multi_buffer_editor.update(cx, |editor, cx| {
20321        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20322    });
20323
20324    assert_eq!(
20325        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20326        "\n\n\n\n\n\n7777\n8888\n9999",
20327        "After folding the second buffer, its text should not be displayed"
20328    );
20329
20330    multi_buffer_editor.update(cx, |editor, cx| {
20331        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20332    });
20333    assert_eq!(
20334        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20335        "\n\n\n\n\n",
20336        "After folding the third buffer, its text should not be displayed"
20337    );
20338
20339    multi_buffer_editor.update(cx, |editor, cx| {
20340        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20341    });
20342    assert_eq!(
20343        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20344        "\n\n\n\n4444\n5555\n6666\n\n",
20345        "After unfolding the second buffer, its text should be displayed"
20346    );
20347
20348    multi_buffer_editor.update(cx, |editor, cx| {
20349        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20350    });
20351    assert_eq!(
20352        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20353        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20354        "After unfolding the first buffer, its text should be displayed"
20355    );
20356
20357    multi_buffer_editor.update(cx, |editor, cx| {
20358        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20359    });
20360    assert_eq!(
20361        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20362        full_text,
20363        "After unfolding all buffers, all original text should be displayed"
20364    );
20365}
20366
20367#[gpui::test]
20368async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20369    init_test(cx, |_| {});
20370
20371    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20372
20373    let fs = FakeFs::new(cx.executor());
20374    fs.insert_tree(
20375        path!("/a"),
20376        json!({
20377            "main.rs": sample_text,
20378        }),
20379    )
20380    .await;
20381    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20382    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20383    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20384    let worktree = project.update(cx, |project, cx| {
20385        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20386        assert_eq!(worktrees.len(), 1);
20387        worktrees.pop().unwrap()
20388    });
20389    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20390
20391    let buffer_1 = project
20392        .update(cx, |project, cx| {
20393            project.open_buffer((worktree_id, "main.rs"), cx)
20394        })
20395        .await
20396        .unwrap();
20397
20398    let multi_buffer = cx.new(|cx| {
20399        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20400        multi_buffer.push_excerpts(
20401            buffer_1.clone(),
20402            [ExcerptRange::new(
20403                Point::new(0, 0)
20404                    ..Point::new(
20405                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20406                        0,
20407                    ),
20408            )],
20409            cx,
20410        );
20411        multi_buffer
20412    });
20413    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20414        Editor::new(
20415            EditorMode::full(),
20416            multi_buffer,
20417            Some(project.clone()),
20418            window,
20419            cx,
20420        )
20421    });
20422
20423    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20424    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20425        enum TestHighlight {}
20426        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20427        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20428        editor.highlight_text::<TestHighlight>(
20429            vec![highlight_range.clone()],
20430            HighlightStyle::color(Hsla::green()),
20431            cx,
20432        );
20433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20434            s.select_ranges(Some(highlight_range))
20435        });
20436    });
20437
20438    let full_text = format!("\n\n{sample_text}");
20439    assert_eq!(
20440        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20441        full_text,
20442    );
20443}
20444
20445#[gpui::test]
20446async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20447    init_test(cx, |_| {});
20448    cx.update(|cx| {
20449        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20450            "keymaps/default-linux.json",
20451            cx,
20452        )
20453        .unwrap();
20454        cx.bind_keys(default_key_bindings);
20455    });
20456
20457    let (editor, cx) = cx.add_window_view(|window, cx| {
20458        let multi_buffer = MultiBuffer::build_multi(
20459            [
20460                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20461                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20462                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20463                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20464            ],
20465            cx,
20466        );
20467        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20468
20469        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20470        // fold all but the second buffer, so that we test navigating between two
20471        // adjacent folded buffers, as well as folded buffers at the start and
20472        // end the multibuffer
20473        editor.fold_buffer(buffer_ids[0], cx);
20474        editor.fold_buffer(buffer_ids[2], cx);
20475        editor.fold_buffer(buffer_ids[3], cx);
20476
20477        editor
20478    });
20479    cx.simulate_resize(size(px(1000.), px(1000.)));
20480
20481    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20482    cx.assert_excerpts_with_selections(indoc! {"
20483        [EXCERPT]
20484        ˇ[FOLDED]
20485        [EXCERPT]
20486        a1
20487        b1
20488        [EXCERPT]
20489        [FOLDED]
20490        [EXCERPT]
20491        [FOLDED]
20492        "
20493    });
20494    cx.simulate_keystroke("down");
20495    cx.assert_excerpts_with_selections(indoc! {"
20496        [EXCERPT]
20497        [FOLDED]
20498        [EXCERPT]
20499        ˇa1
20500        b1
20501        [EXCERPT]
20502        [FOLDED]
20503        [EXCERPT]
20504        [FOLDED]
20505        "
20506    });
20507    cx.simulate_keystroke("down");
20508    cx.assert_excerpts_with_selections(indoc! {"
20509        [EXCERPT]
20510        [FOLDED]
20511        [EXCERPT]
20512        a1
20513        ˇb1
20514        [EXCERPT]
20515        [FOLDED]
20516        [EXCERPT]
20517        [FOLDED]
20518        "
20519    });
20520    cx.simulate_keystroke("down");
20521    cx.assert_excerpts_with_selections(indoc! {"
20522        [EXCERPT]
20523        [FOLDED]
20524        [EXCERPT]
20525        a1
20526        b1
20527        ˇ[EXCERPT]
20528        [FOLDED]
20529        [EXCERPT]
20530        [FOLDED]
20531        "
20532    });
20533    cx.simulate_keystroke("down");
20534    cx.assert_excerpts_with_selections(indoc! {"
20535        [EXCERPT]
20536        [FOLDED]
20537        [EXCERPT]
20538        a1
20539        b1
20540        [EXCERPT]
20541        ˇ[FOLDED]
20542        [EXCERPT]
20543        [FOLDED]
20544        "
20545    });
20546    for _ in 0..5 {
20547        cx.simulate_keystroke("down");
20548        cx.assert_excerpts_with_selections(indoc! {"
20549            [EXCERPT]
20550            [FOLDED]
20551            [EXCERPT]
20552            a1
20553            b1
20554            [EXCERPT]
20555            [FOLDED]
20556            [EXCERPT]
20557            ˇ[FOLDED]
20558            "
20559        });
20560    }
20561
20562    cx.simulate_keystroke("up");
20563    cx.assert_excerpts_with_selections(indoc! {"
20564        [EXCERPT]
20565        [FOLDED]
20566        [EXCERPT]
20567        a1
20568        b1
20569        [EXCERPT]
20570        ˇ[FOLDED]
20571        [EXCERPT]
20572        [FOLDED]
20573        "
20574    });
20575    cx.simulate_keystroke("up");
20576    cx.assert_excerpts_with_selections(indoc! {"
20577        [EXCERPT]
20578        [FOLDED]
20579        [EXCERPT]
20580        a1
20581        b1
20582        ˇ[EXCERPT]
20583        [FOLDED]
20584        [EXCERPT]
20585        [FOLDED]
20586        "
20587    });
20588    cx.simulate_keystroke("up");
20589    cx.assert_excerpts_with_selections(indoc! {"
20590        [EXCERPT]
20591        [FOLDED]
20592        [EXCERPT]
20593        a1
20594        ˇb1
20595        [EXCERPT]
20596        [FOLDED]
20597        [EXCERPT]
20598        [FOLDED]
20599        "
20600    });
20601    cx.simulate_keystroke("up");
20602    cx.assert_excerpts_with_selections(indoc! {"
20603        [EXCERPT]
20604        [FOLDED]
20605        [EXCERPT]
20606        ˇa1
20607        b1
20608        [EXCERPT]
20609        [FOLDED]
20610        [EXCERPT]
20611        [FOLDED]
20612        "
20613    });
20614    for _ in 0..5 {
20615        cx.simulate_keystroke("up");
20616        cx.assert_excerpts_with_selections(indoc! {"
20617            [EXCERPT]
20618            ˇ[FOLDED]
20619            [EXCERPT]
20620            a1
20621            b1
20622            [EXCERPT]
20623            [FOLDED]
20624            [EXCERPT]
20625            [FOLDED]
20626            "
20627        });
20628    }
20629}
20630
20631#[gpui::test]
20632async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20633    init_test(cx, |_| {});
20634
20635    // Simple insertion
20636    assert_highlighted_edits(
20637        "Hello, world!",
20638        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20639        true,
20640        cx,
20641        |highlighted_edits, cx| {
20642            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20643            assert_eq!(highlighted_edits.highlights.len(), 1);
20644            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20645            assert_eq!(
20646                highlighted_edits.highlights[0].1.background_color,
20647                Some(cx.theme().status().created_background)
20648            );
20649        },
20650    )
20651    .await;
20652
20653    // Replacement
20654    assert_highlighted_edits(
20655        "This is a test.",
20656        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20657        false,
20658        cx,
20659        |highlighted_edits, cx| {
20660            assert_eq!(highlighted_edits.text, "That is a test.");
20661            assert_eq!(highlighted_edits.highlights.len(), 1);
20662            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20663            assert_eq!(
20664                highlighted_edits.highlights[0].1.background_color,
20665                Some(cx.theme().status().created_background)
20666            );
20667        },
20668    )
20669    .await;
20670
20671    // Multiple edits
20672    assert_highlighted_edits(
20673        "Hello, world!",
20674        vec![
20675            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20676            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20677        ],
20678        false,
20679        cx,
20680        |highlighted_edits, cx| {
20681            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20682            assert_eq!(highlighted_edits.highlights.len(), 2);
20683            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20684            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20685            assert_eq!(
20686                highlighted_edits.highlights[0].1.background_color,
20687                Some(cx.theme().status().created_background)
20688            );
20689            assert_eq!(
20690                highlighted_edits.highlights[1].1.background_color,
20691                Some(cx.theme().status().created_background)
20692            );
20693        },
20694    )
20695    .await;
20696
20697    // Multiple lines with edits
20698    assert_highlighted_edits(
20699        "First line\nSecond line\nThird line\nFourth line",
20700        vec![
20701            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20702            (
20703                Point::new(2, 0)..Point::new(2, 10),
20704                "New third line".to_string(),
20705            ),
20706            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20707        ],
20708        false,
20709        cx,
20710        |highlighted_edits, cx| {
20711            assert_eq!(
20712                highlighted_edits.text,
20713                "Second modified\nNew third line\nFourth updated line"
20714            );
20715            assert_eq!(highlighted_edits.highlights.len(), 3);
20716            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20717            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20718            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20719            for highlight in &highlighted_edits.highlights {
20720                assert_eq!(
20721                    highlight.1.background_color,
20722                    Some(cx.theme().status().created_background)
20723                );
20724            }
20725        },
20726    )
20727    .await;
20728}
20729
20730#[gpui::test]
20731async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20732    init_test(cx, |_| {});
20733
20734    // Deletion
20735    assert_highlighted_edits(
20736        "Hello, world!",
20737        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20738        true,
20739        cx,
20740        |highlighted_edits, cx| {
20741            assert_eq!(highlighted_edits.text, "Hello, world!");
20742            assert_eq!(highlighted_edits.highlights.len(), 1);
20743            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20744            assert_eq!(
20745                highlighted_edits.highlights[0].1.background_color,
20746                Some(cx.theme().status().deleted_background)
20747            );
20748        },
20749    )
20750    .await;
20751
20752    // Insertion
20753    assert_highlighted_edits(
20754        "Hello, world!",
20755        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20756        true,
20757        cx,
20758        |highlighted_edits, cx| {
20759            assert_eq!(highlighted_edits.highlights.len(), 1);
20760            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20761            assert_eq!(
20762                highlighted_edits.highlights[0].1.background_color,
20763                Some(cx.theme().status().created_background)
20764            );
20765        },
20766    )
20767    .await;
20768}
20769
20770async fn assert_highlighted_edits(
20771    text: &str,
20772    edits: Vec<(Range<Point>, String)>,
20773    include_deletions: bool,
20774    cx: &mut TestAppContext,
20775    assertion_fn: impl Fn(HighlightedText, &App),
20776) {
20777    let window = cx.add_window(|window, cx| {
20778        let buffer = MultiBuffer::build_simple(text, cx);
20779        Editor::new(EditorMode::full(), buffer, None, window, cx)
20780    });
20781    let cx = &mut VisualTestContext::from_window(*window, cx);
20782
20783    let (buffer, snapshot) = window
20784        .update(cx, |editor, _window, cx| {
20785            (
20786                editor.buffer().clone(),
20787                editor.buffer().read(cx).snapshot(cx),
20788            )
20789        })
20790        .unwrap();
20791
20792    let edits = edits
20793        .into_iter()
20794        .map(|(range, edit)| {
20795            (
20796                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20797                edit,
20798            )
20799        })
20800        .collect::<Vec<_>>();
20801
20802    let text_anchor_edits = edits
20803        .clone()
20804        .into_iter()
20805        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20806        .collect::<Vec<_>>();
20807
20808    let edit_preview = window
20809        .update(cx, |_, _window, cx| {
20810            buffer
20811                .read(cx)
20812                .as_singleton()
20813                .unwrap()
20814                .read(cx)
20815                .preview_edits(text_anchor_edits.into(), cx)
20816        })
20817        .unwrap()
20818        .await;
20819
20820    cx.update(|_window, cx| {
20821        let highlighted_edits = edit_prediction_edit_text(
20822            &snapshot.as_singleton().unwrap().2,
20823            &edits,
20824            &edit_preview,
20825            include_deletions,
20826            cx,
20827        );
20828        assertion_fn(highlighted_edits, cx)
20829    });
20830}
20831
20832#[track_caller]
20833fn assert_breakpoint(
20834    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20835    path: &Arc<Path>,
20836    expected: Vec<(u32, Breakpoint)>,
20837) {
20838    if expected.len() == 0usize {
20839        assert!(!breakpoints.contains_key(path), "{}", path.display());
20840    } else {
20841        let mut breakpoint = breakpoints
20842            .get(path)
20843            .unwrap()
20844            .into_iter()
20845            .map(|breakpoint| {
20846                (
20847                    breakpoint.row,
20848                    Breakpoint {
20849                        message: breakpoint.message.clone(),
20850                        state: breakpoint.state,
20851                        condition: breakpoint.condition.clone(),
20852                        hit_condition: breakpoint.hit_condition.clone(),
20853                    },
20854                )
20855            })
20856            .collect::<Vec<_>>();
20857
20858        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20859
20860        assert_eq!(expected, breakpoint);
20861    }
20862}
20863
20864fn add_log_breakpoint_at_cursor(
20865    editor: &mut Editor,
20866    log_message: &str,
20867    window: &mut Window,
20868    cx: &mut Context<Editor>,
20869) {
20870    let (anchor, bp) = editor
20871        .breakpoints_at_cursors(window, cx)
20872        .first()
20873        .and_then(|(anchor, bp)| {
20874            if let Some(bp) = bp {
20875                Some((*anchor, bp.clone()))
20876            } else {
20877                None
20878            }
20879        })
20880        .unwrap_or_else(|| {
20881            let cursor_position: Point = editor.selections.newest(cx).head();
20882
20883            let breakpoint_position = editor
20884                .snapshot(window, cx)
20885                .display_snapshot
20886                .buffer_snapshot
20887                .anchor_before(Point::new(cursor_position.row, 0));
20888
20889            (breakpoint_position, Breakpoint::new_log(&log_message))
20890        });
20891
20892    editor.edit_breakpoint_at_anchor(
20893        anchor,
20894        bp,
20895        BreakpointEditAction::EditLogMessage(log_message.into()),
20896        cx,
20897    );
20898}
20899
20900#[gpui::test]
20901async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20902    init_test(cx, |_| {});
20903
20904    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20905    let fs = FakeFs::new(cx.executor());
20906    fs.insert_tree(
20907        path!("/a"),
20908        json!({
20909            "main.rs": sample_text,
20910        }),
20911    )
20912    .await;
20913    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20914    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20915    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20916
20917    let fs = FakeFs::new(cx.executor());
20918    fs.insert_tree(
20919        path!("/a"),
20920        json!({
20921            "main.rs": sample_text,
20922        }),
20923    )
20924    .await;
20925    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20926    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20927    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20928    let worktree_id = workspace
20929        .update(cx, |workspace, _window, cx| {
20930            workspace.project().update(cx, |project, cx| {
20931                project.worktrees(cx).next().unwrap().read(cx).id()
20932            })
20933        })
20934        .unwrap();
20935
20936    let buffer = project
20937        .update(cx, |project, cx| {
20938            project.open_buffer((worktree_id, "main.rs"), cx)
20939        })
20940        .await
20941        .unwrap();
20942
20943    let (editor, cx) = cx.add_window_view(|window, cx| {
20944        Editor::new(
20945            EditorMode::full(),
20946            MultiBuffer::build_from_buffer(buffer, cx),
20947            Some(project.clone()),
20948            window,
20949            cx,
20950        )
20951    });
20952
20953    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20954    let abs_path = project.read_with(cx, |project, cx| {
20955        project
20956            .absolute_path(&project_path, cx)
20957            .map(|path_buf| Arc::from(path_buf.to_owned()))
20958            .unwrap()
20959    });
20960
20961    // assert we can add breakpoint on the first line
20962    editor.update_in(cx, |editor, window, cx| {
20963        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20964        editor.move_to_end(&MoveToEnd, window, cx);
20965        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20966    });
20967
20968    let breakpoints = editor.update(cx, |editor, cx| {
20969        editor
20970            .breakpoint_store()
20971            .as_ref()
20972            .unwrap()
20973            .read(cx)
20974            .all_source_breakpoints(cx)
20975            .clone()
20976    });
20977
20978    assert_eq!(1, breakpoints.len());
20979    assert_breakpoint(
20980        &breakpoints,
20981        &abs_path,
20982        vec![
20983            (0, Breakpoint::new_standard()),
20984            (3, Breakpoint::new_standard()),
20985        ],
20986    );
20987
20988    editor.update_in(cx, |editor, window, cx| {
20989        editor.move_to_beginning(&MoveToBeginning, window, cx);
20990        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20991    });
20992
20993    let breakpoints = editor.update(cx, |editor, cx| {
20994        editor
20995            .breakpoint_store()
20996            .as_ref()
20997            .unwrap()
20998            .read(cx)
20999            .all_source_breakpoints(cx)
21000            .clone()
21001    });
21002
21003    assert_eq!(1, breakpoints.len());
21004    assert_breakpoint(
21005        &breakpoints,
21006        &abs_path,
21007        vec![(3, Breakpoint::new_standard())],
21008    );
21009
21010    editor.update_in(cx, |editor, window, cx| {
21011        editor.move_to_end(&MoveToEnd, window, cx);
21012        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21013    });
21014
21015    let breakpoints = editor.update(cx, |editor, cx| {
21016        editor
21017            .breakpoint_store()
21018            .as_ref()
21019            .unwrap()
21020            .read(cx)
21021            .all_source_breakpoints(cx)
21022            .clone()
21023    });
21024
21025    assert_eq!(0, breakpoints.len());
21026    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21027}
21028
21029#[gpui::test]
21030async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21031    init_test(cx, |_| {});
21032
21033    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21034
21035    let fs = FakeFs::new(cx.executor());
21036    fs.insert_tree(
21037        path!("/a"),
21038        json!({
21039            "main.rs": sample_text,
21040        }),
21041    )
21042    .await;
21043    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21044    let (workspace, cx) =
21045        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21046
21047    let worktree_id = workspace.update(cx, |workspace, cx| {
21048        workspace.project().update(cx, |project, cx| {
21049            project.worktrees(cx).next().unwrap().read(cx).id()
21050        })
21051    });
21052
21053    let buffer = project
21054        .update(cx, |project, cx| {
21055            project.open_buffer((worktree_id, "main.rs"), cx)
21056        })
21057        .await
21058        .unwrap();
21059
21060    let (editor, cx) = cx.add_window_view(|window, cx| {
21061        Editor::new(
21062            EditorMode::full(),
21063            MultiBuffer::build_from_buffer(buffer, cx),
21064            Some(project.clone()),
21065            window,
21066            cx,
21067        )
21068    });
21069
21070    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21071    let abs_path = project.read_with(cx, |project, cx| {
21072        project
21073            .absolute_path(&project_path, cx)
21074            .map(|path_buf| Arc::from(path_buf.to_owned()))
21075            .unwrap()
21076    });
21077
21078    editor.update_in(cx, |editor, window, cx| {
21079        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21080    });
21081
21082    let breakpoints = editor.update(cx, |editor, cx| {
21083        editor
21084            .breakpoint_store()
21085            .as_ref()
21086            .unwrap()
21087            .read(cx)
21088            .all_source_breakpoints(cx)
21089            .clone()
21090    });
21091
21092    assert_breakpoint(
21093        &breakpoints,
21094        &abs_path,
21095        vec![(0, Breakpoint::new_log("hello world"))],
21096    );
21097
21098    // Removing a log message from a log breakpoint should remove it
21099    editor.update_in(cx, |editor, window, cx| {
21100        add_log_breakpoint_at_cursor(editor, "", window, cx);
21101    });
21102
21103    let breakpoints = editor.update(cx, |editor, cx| {
21104        editor
21105            .breakpoint_store()
21106            .as_ref()
21107            .unwrap()
21108            .read(cx)
21109            .all_source_breakpoints(cx)
21110            .clone()
21111    });
21112
21113    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21114
21115    editor.update_in(cx, |editor, window, cx| {
21116        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21117        editor.move_to_end(&MoveToEnd, window, cx);
21118        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21119        // Not adding a log message to a standard breakpoint shouldn't remove it
21120        add_log_breakpoint_at_cursor(editor, "", window, cx);
21121    });
21122
21123    let breakpoints = editor.update(cx, |editor, cx| {
21124        editor
21125            .breakpoint_store()
21126            .as_ref()
21127            .unwrap()
21128            .read(cx)
21129            .all_source_breakpoints(cx)
21130            .clone()
21131    });
21132
21133    assert_breakpoint(
21134        &breakpoints,
21135        &abs_path,
21136        vec![
21137            (0, Breakpoint::new_standard()),
21138            (3, Breakpoint::new_standard()),
21139        ],
21140    );
21141
21142    editor.update_in(cx, |editor, window, cx| {
21143        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21144    });
21145
21146    let breakpoints = editor.update(cx, |editor, cx| {
21147        editor
21148            .breakpoint_store()
21149            .as_ref()
21150            .unwrap()
21151            .read(cx)
21152            .all_source_breakpoints(cx)
21153            .clone()
21154    });
21155
21156    assert_breakpoint(
21157        &breakpoints,
21158        &abs_path,
21159        vec![
21160            (0, Breakpoint::new_standard()),
21161            (3, Breakpoint::new_log("hello world")),
21162        ],
21163    );
21164
21165    editor.update_in(cx, |editor, window, cx| {
21166        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21167    });
21168
21169    let breakpoints = editor.update(cx, |editor, cx| {
21170        editor
21171            .breakpoint_store()
21172            .as_ref()
21173            .unwrap()
21174            .read(cx)
21175            .all_source_breakpoints(cx)
21176            .clone()
21177    });
21178
21179    assert_breakpoint(
21180        &breakpoints,
21181        &abs_path,
21182        vec![
21183            (0, Breakpoint::new_standard()),
21184            (3, Breakpoint::new_log("hello Earth!!")),
21185        ],
21186    );
21187}
21188
21189/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21190/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21191/// or when breakpoints were placed out of order. This tests for a regression too
21192#[gpui::test]
21193async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21194    init_test(cx, |_| {});
21195
21196    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21197    let fs = FakeFs::new(cx.executor());
21198    fs.insert_tree(
21199        path!("/a"),
21200        json!({
21201            "main.rs": sample_text,
21202        }),
21203    )
21204    .await;
21205    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21206    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21207    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21208
21209    let fs = FakeFs::new(cx.executor());
21210    fs.insert_tree(
21211        path!("/a"),
21212        json!({
21213            "main.rs": sample_text,
21214        }),
21215    )
21216    .await;
21217    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21218    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21219    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21220    let worktree_id = workspace
21221        .update(cx, |workspace, _window, cx| {
21222            workspace.project().update(cx, |project, cx| {
21223                project.worktrees(cx).next().unwrap().read(cx).id()
21224            })
21225        })
21226        .unwrap();
21227
21228    let buffer = project
21229        .update(cx, |project, cx| {
21230            project.open_buffer((worktree_id, "main.rs"), cx)
21231        })
21232        .await
21233        .unwrap();
21234
21235    let (editor, cx) = cx.add_window_view(|window, cx| {
21236        Editor::new(
21237            EditorMode::full(),
21238            MultiBuffer::build_from_buffer(buffer, cx),
21239            Some(project.clone()),
21240            window,
21241            cx,
21242        )
21243    });
21244
21245    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21246    let abs_path = project.read_with(cx, |project, cx| {
21247        project
21248            .absolute_path(&project_path, cx)
21249            .map(|path_buf| Arc::from(path_buf.to_owned()))
21250            .unwrap()
21251    });
21252
21253    // assert we can add breakpoint on the first line
21254    editor.update_in(cx, |editor, window, cx| {
21255        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21256        editor.move_to_end(&MoveToEnd, window, cx);
21257        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21258        editor.move_up(&MoveUp, window, cx);
21259        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21260    });
21261
21262    let breakpoints = editor.update(cx, |editor, cx| {
21263        editor
21264            .breakpoint_store()
21265            .as_ref()
21266            .unwrap()
21267            .read(cx)
21268            .all_source_breakpoints(cx)
21269            .clone()
21270    });
21271
21272    assert_eq!(1, breakpoints.len());
21273    assert_breakpoint(
21274        &breakpoints,
21275        &abs_path,
21276        vec![
21277            (0, Breakpoint::new_standard()),
21278            (2, Breakpoint::new_standard()),
21279            (3, Breakpoint::new_standard()),
21280        ],
21281    );
21282
21283    editor.update_in(cx, |editor, window, cx| {
21284        editor.move_to_beginning(&MoveToBeginning, window, cx);
21285        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21286        editor.move_to_end(&MoveToEnd, window, cx);
21287        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21288        // Disabling a breakpoint that doesn't exist should do nothing
21289        editor.move_up(&MoveUp, window, cx);
21290        editor.move_up(&MoveUp, window, cx);
21291        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21292    });
21293
21294    let breakpoints = editor.update(cx, |editor, cx| {
21295        editor
21296            .breakpoint_store()
21297            .as_ref()
21298            .unwrap()
21299            .read(cx)
21300            .all_source_breakpoints(cx)
21301            .clone()
21302    });
21303
21304    let disable_breakpoint = {
21305        let mut bp = Breakpoint::new_standard();
21306        bp.state = BreakpointState::Disabled;
21307        bp
21308    };
21309
21310    assert_eq!(1, breakpoints.len());
21311    assert_breakpoint(
21312        &breakpoints,
21313        &abs_path,
21314        vec![
21315            (0, disable_breakpoint.clone()),
21316            (2, Breakpoint::new_standard()),
21317            (3, disable_breakpoint.clone()),
21318        ],
21319    );
21320
21321    editor.update_in(cx, |editor, window, cx| {
21322        editor.move_to_beginning(&MoveToBeginning, window, cx);
21323        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21324        editor.move_to_end(&MoveToEnd, window, cx);
21325        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21326        editor.move_up(&MoveUp, window, cx);
21327        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21328    });
21329
21330    let breakpoints = editor.update(cx, |editor, cx| {
21331        editor
21332            .breakpoint_store()
21333            .as_ref()
21334            .unwrap()
21335            .read(cx)
21336            .all_source_breakpoints(cx)
21337            .clone()
21338    });
21339
21340    assert_eq!(1, breakpoints.len());
21341    assert_breakpoint(
21342        &breakpoints,
21343        &abs_path,
21344        vec![
21345            (0, Breakpoint::new_standard()),
21346            (2, disable_breakpoint),
21347            (3, Breakpoint::new_standard()),
21348        ],
21349    );
21350}
21351
21352#[gpui::test]
21353async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21354    init_test(cx, |_| {});
21355    let capabilities = lsp::ServerCapabilities {
21356        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21357            prepare_provider: Some(true),
21358            work_done_progress_options: Default::default(),
21359        })),
21360        ..Default::default()
21361    };
21362    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21363
21364    cx.set_state(indoc! {"
21365        struct Fˇoo {}
21366    "});
21367
21368    cx.update_editor(|editor, _, cx| {
21369        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21370        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21371        editor.highlight_background::<DocumentHighlightRead>(
21372            &[highlight_range],
21373            |theme| theme.colors().editor_document_highlight_read_background,
21374            cx,
21375        );
21376    });
21377
21378    let mut prepare_rename_handler = cx
21379        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21380            move |_, _, _| async move {
21381                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21382                    start: lsp::Position {
21383                        line: 0,
21384                        character: 7,
21385                    },
21386                    end: lsp::Position {
21387                        line: 0,
21388                        character: 10,
21389                    },
21390                })))
21391            },
21392        );
21393    let prepare_rename_task = cx
21394        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21395        .expect("Prepare rename was not started");
21396    prepare_rename_handler.next().await.unwrap();
21397    prepare_rename_task.await.expect("Prepare rename failed");
21398
21399    let mut rename_handler =
21400        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21401            let edit = lsp::TextEdit {
21402                range: lsp::Range {
21403                    start: lsp::Position {
21404                        line: 0,
21405                        character: 7,
21406                    },
21407                    end: lsp::Position {
21408                        line: 0,
21409                        character: 10,
21410                    },
21411                },
21412                new_text: "FooRenamed".to_string(),
21413            };
21414            Ok(Some(lsp::WorkspaceEdit::new(
21415                // Specify the same edit twice
21416                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21417            )))
21418        });
21419    let rename_task = cx
21420        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21421        .expect("Confirm rename was not started");
21422    rename_handler.next().await.unwrap();
21423    rename_task.await.expect("Confirm rename failed");
21424    cx.run_until_parked();
21425
21426    // Despite two edits, only one is actually applied as those are identical
21427    cx.assert_editor_state(indoc! {"
21428        struct FooRenamedˇ {}
21429    "});
21430}
21431
21432#[gpui::test]
21433async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21434    init_test(cx, |_| {});
21435    // These capabilities indicate that the server does not support prepare rename.
21436    let capabilities = lsp::ServerCapabilities {
21437        rename_provider: Some(lsp::OneOf::Left(true)),
21438        ..Default::default()
21439    };
21440    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21441
21442    cx.set_state(indoc! {"
21443        struct Fˇoo {}
21444    "});
21445
21446    cx.update_editor(|editor, _window, cx| {
21447        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21448        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21449        editor.highlight_background::<DocumentHighlightRead>(
21450            &[highlight_range],
21451            |theme| theme.colors().editor_document_highlight_read_background,
21452            cx,
21453        );
21454    });
21455
21456    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21457        .expect("Prepare rename was not started")
21458        .await
21459        .expect("Prepare rename failed");
21460
21461    let mut rename_handler =
21462        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21463            let edit = lsp::TextEdit {
21464                range: lsp::Range {
21465                    start: lsp::Position {
21466                        line: 0,
21467                        character: 7,
21468                    },
21469                    end: lsp::Position {
21470                        line: 0,
21471                        character: 10,
21472                    },
21473                },
21474                new_text: "FooRenamed".to_string(),
21475            };
21476            Ok(Some(lsp::WorkspaceEdit::new(
21477                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21478            )))
21479        });
21480    let rename_task = cx
21481        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21482        .expect("Confirm rename was not started");
21483    rename_handler.next().await.unwrap();
21484    rename_task.await.expect("Confirm rename failed");
21485    cx.run_until_parked();
21486
21487    // Correct range is renamed, as `surrounding_word` is used to find it.
21488    cx.assert_editor_state(indoc! {"
21489        struct FooRenamedˇ {}
21490    "});
21491}
21492
21493#[gpui::test]
21494async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21495    init_test(cx, |_| {});
21496    let mut cx = EditorTestContext::new(cx).await;
21497
21498    let language = Arc::new(
21499        Language::new(
21500            LanguageConfig::default(),
21501            Some(tree_sitter_html::LANGUAGE.into()),
21502        )
21503        .with_brackets_query(
21504            r#"
21505            ("<" @open "/>" @close)
21506            ("</" @open ">" @close)
21507            ("<" @open ">" @close)
21508            ("\"" @open "\"" @close)
21509            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21510        "#,
21511        )
21512        .unwrap(),
21513    );
21514    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21515
21516    cx.set_state(indoc! {"
21517        <span>ˇ</span>
21518    "});
21519    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21520    cx.assert_editor_state(indoc! {"
21521        <span>
21522        ˇ
21523        </span>
21524    "});
21525
21526    cx.set_state(indoc! {"
21527        <span><span></span>ˇ</span>
21528    "});
21529    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21530    cx.assert_editor_state(indoc! {"
21531        <span><span></span>
21532        ˇ</span>
21533    "});
21534
21535    cx.set_state(indoc! {"
21536        <span>ˇ
21537        </span>
21538    "});
21539    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21540    cx.assert_editor_state(indoc! {"
21541        <span>
21542        ˇ
21543        </span>
21544    "});
21545}
21546
21547#[gpui::test(iterations = 10)]
21548async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21549    init_test(cx, |_| {});
21550
21551    let fs = FakeFs::new(cx.executor());
21552    fs.insert_tree(
21553        path!("/dir"),
21554        json!({
21555            "a.ts": "a",
21556        }),
21557    )
21558    .await;
21559
21560    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21561    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21562    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21563
21564    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21565    language_registry.add(Arc::new(Language::new(
21566        LanguageConfig {
21567            name: "TypeScript".into(),
21568            matcher: LanguageMatcher {
21569                path_suffixes: vec!["ts".to_string()],
21570                ..Default::default()
21571            },
21572            ..Default::default()
21573        },
21574        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21575    )));
21576    let mut fake_language_servers = language_registry.register_fake_lsp(
21577        "TypeScript",
21578        FakeLspAdapter {
21579            capabilities: lsp::ServerCapabilities {
21580                code_lens_provider: Some(lsp::CodeLensOptions {
21581                    resolve_provider: Some(true),
21582                }),
21583                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21584                    commands: vec!["_the/command".to_string()],
21585                    ..lsp::ExecuteCommandOptions::default()
21586                }),
21587                ..lsp::ServerCapabilities::default()
21588            },
21589            ..FakeLspAdapter::default()
21590        },
21591    );
21592
21593    let editor = workspace
21594        .update(cx, |workspace, window, cx| {
21595            workspace.open_abs_path(
21596                PathBuf::from(path!("/dir/a.ts")),
21597                OpenOptions::default(),
21598                window,
21599                cx,
21600            )
21601        })
21602        .unwrap()
21603        .await
21604        .unwrap()
21605        .downcast::<Editor>()
21606        .unwrap();
21607    cx.executor().run_until_parked();
21608
21609    let fake_server = fake_language_servers.next().await.unwrap();
21610
21611    let buffer = editor.update(cx, |editor, cx| {
21612        editor
21613            .buffer()
21614            .read(cx)
21615            .as_singleton()
21616            .expect("have opened a single file by path")
21617    });
21618
21619    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21620    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21621    drop(buffer_snapshot);
21622    let actions = cx
21623        .update_window(*workspace, |_, window, cx| {
21624            project.code_actions(&buffer, anchor..anchor, window, cx)
21625        })
21626        .unwrap();
21627
21628    fake_server
21629        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21630            Ok(Some(vec![
21631                lsp::CodeLens {
21632                    range: lsp::Range::default(),
21633                    command: Some(lsp::Command {
21634                        title: "Code lens command".to_owned(),
21635                        command: "_the/command".to_owned(),
21636                        arguments: None,
21637                    }),
21638                    data: None,
21639                },
21640                lsp::CodeLens {
21641                    range: lsp::Range::default(),
21642                    command: Some(lsp::Command {
21643                        title: "Command not in capabilities".to_owned(),
21644                        command: "not in capabilities".to_owned(),
21645                        arguments: None,
21646                    }),
21647                    data: None,
21648                },
21649                lsp::CodeLens {
21650                    range: lsp::Range {
21651                        start: lsp::Position {
21652                            line: 1,
21653                            character: 1,
21654                        },
21655                        end: lsp::Position {
21656                            line: 1,
21657                            character: 1,
21658                        },
21659                    },
21660                    command: Some(lsp::Command {
21661                        title: "Command not in range".to_owned(),
21662                        command: "_the/command".to_owned(),
21663                        arguments: None,
21664                    }),
21665                    data: None,
21666                },
21667            ]))
21668        })
21669        .next()
21670        .await;
21671
21672    let actions = actions.await.unwrap();
21673    assert_eq!(
21674        actions.len(),
21675        1,
21676        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21677    );
21678    let action = actions[0].clone();
21679    let apply = project.update(cx, |project, cx| {
21680        project.apply_code_action(buffer.clone(), action, true, cx)
21681    });
21682
21683    // Resolving the code action does not populate its edits. In absence of
21684    // edits, we must execute the given command.
21685    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21686        |mut lens, _| async move {
21687            let lens_command = lens.command.as_mut().expect("should have a command");
21688            assert_eq!(lens_command.title, "Code lens command");
21689            lens_command.arguments = Some(vec![json!("the-argument")]);
21690            Ok(lens)
21691        },
21692    );
21693
21694    // While executing the command, the language server sends the editor
21695    // a `workspaceEdit` request.
21696    fake_server
21697        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21698            let fake = fake_server.clone();
21699            move |params, _| {
21700                assert_eq!(params.command, "_the/command");
21701                let fake = fake.clone();
21702                async move {
21703                    fake.server
21704                        .request::<lsp::request::ApplyWorkspaceEdit>(
21705                            lsp::ApplyWorkspaceEditParams {
21706                                label: None,
21707                                edit: lsp::WorkspaceEdit {
21708                                    changes: Some(
21709                                        [(
21710                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21711                                            vec![lsp::TextEdit {
21712                                                range: lsp::Range::new(
21713                                                    lsp::Position::new(0, 0),
21714                                                    lsp::Position::new(0, 0),
21715                                                ),
21716                                                new_text: "X".into(),
21717                                            }],
21718                                        )]
21719                                        .into_iter()
21720                                        .collect(),
21721                                    ),
21722                                    ..lsp::WorkspaceEdit::default()
21723                                },
21724                            },
21725                        )
21726                        .await
21727                        .into_response()
21728                        .unwrap();
21729                    Ok(Some(json!(null)))
21730                }
21731            }
21732        })
21733        .next()
21734        .await;
21735
21736    // Applying the code lens command returns a project transaction containing the edits
21737    // sent by the language server in its `workspaceEdit` request.
21738    let transaction = apply.await.unwrap();
21739    assert!(transaction.0.contains_key(&buffer));
21740    buffer.update(cx, |buffer, cx| {
21741        assert_eq!(buffer.text(), "Xa");
21742        buffer.undo(cx);
21743        assert_eq!(buffer.text(), "a");
21744    });
21745
21746    let actions_after_edits = cx
21747        .update_window(*workspace, |_, window, cx| {
21748            project.code_actions(&buffer, anchor..anchor, window, cx)
21749        })
21750        .unwrap()
21751        .await
21752        .unwrap();
21753    assert_eq!(
21754        actions, actions_after_edits,
21755        "For the same selection, same code lens actions should be returned"
21756    );
21757
21758    let _responses =
21759        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21760            panic!("No more code lens requests are expected");
21761        });
21762    editor.update_in(cx, |editor, window, cx| {
21763        editor.select_all(&SelectAll, window, cx);
21764    });
21765    cx.executor().run_until_parked();
21766    let new_actions = cx
21767        .update_window(*workspace, |_, window, cx| {
21768            project.code_actions(&buffer, anchor..anchor, window, cx)
21769        })
21770        .unwrap()
21771        .await
21772        .unwrap();
21773    assert_eq!(
21774        actions, new_actions,
21775        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21776    );
21777}
21778
21779#[gpui::test]
21780async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21781    init_test(cx, |_| {});
21782
21783    let fs = FakeFs::new(cx.executor());
21784    let main_text = r#"fn main() {
21785println!("1");
21786println!("2");
21787println!("3");
21788println!("4");
21789println!("5");
21790}"#;
21791    let lib_text = "mod foo {}";
21792    fs.insert_tree(
21793        path!("/a"),
21794        json!({
21795            "lib.rs": lib_text,
21796            "main.rs": main_text,
21797        }),
21798    )
21799    .await;
21800
21801    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21802    let (workspace, cx) =
21803        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21804    let worktree_id = workspace.update(cx, |workspace, cx| {
21805        workspace.project().update(cx, |project, cx| {
21806            project.worktrees(cx).next().unwrap().read(cx).id()
21807        })
21808    });
21809
21810    let expected_ranges = vec![
21811        Point::new(0, 0)..Point::new(0, 0),
21812        Point::new(1, 0)..Point::new(1, 1),
21813        Point::new(2, 0)..Point::new(2, 2),
21814        Point::new(3, 0)..Point::new(3, 3),
21815    ];
21816
21817    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21818    let editor_1 = workspace
21819        .update_in(cx, |workspace, window, cx| {
21820            workspace.open_path(
21821                (worktree_id, "main.rs"),
21822                Some(pane_1.downgrade()),
21823                true,
21824                window,
21825                cx,
21826            )
21827        })
21828        .unwrap()
21829        .await
21830        .downcast::<Editor>()
21831        .unwrap();
21832    pane_1.update(cx, |pane, cx| {
21833        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21834        open_editor.update(cx, |editor, cx| {
21835            assert_eq!(
21836                editor.display_text(cx),
21837                main_text,
21838                "Original main.rs text on initial open",
21839            );
21840            assert_eq!(
21841                editor
21842                    .selections
21843                    .all::<Point>(cx)
21844                    .into_iter()
21845                    .map(|s| s.range())
21846                    .collect::<Vec<_>>(),
21847                vec![Point::zero()..Point::zero()],
21848                "Default selections on initial open",
21849            );
21850        })
21851    });
21852    editor_1.update_in(cx, |editor, window, cx| {
21853        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21854            s.select_ranges(expected_ranges.clone());
21855        });
21856    });
21857
21858    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21859        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21860    });
21861    let editor_2 = workspace
21862        .update_in(cx, |workspace, window, cx| {
21863            workspace.open_path(
21864                (worktree_id, "main.rs"),
21865                Some(pane_2.downgrade()),
21866                true,
21867                window,
21868                cx,
21869            )
21870        })
21871        .unwrap()
21872        .await
21873        .downcast::<Editor>()
21874        .unwrap();
21875    pane_2.update(cx, |pane, cx| {
21876        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21877        open_editor.update(cx, |editor, cx| {
21878            assert_eq!(
21879                editor.display_text(cx),
21880                main_text,
21881                "Original main.rs text on initial open in another panel",
21882            );
21883            assert_eq!(
21884                editor
21885                    .selections
21886                    .all::<Point>(cx)
21887                    .into_iter()
21888                    .map(|s| s.range())
21889                    .collect::<Vec<_>>(),
21890                vec![Point::zero()..Point::zero()],
21891                "Default selections on initial open in another panel",
21892            );
21893        })
21894    });
21895
21896    editor_2.update_in(cx, |editor, window, cx| {
21897        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21898    });
21899
21900    let _other_editor_1 = workspace
21901        .update_in(cx, |workspace, window, cx| {
21902            workspace.open_path(
21903                (worktree_id, "lib.rs"),
21904                Some(pane_1.downgrade()),
21905                true,
21906                window,
21907                cx,
21908            )
21909        })
21910        .unwrap()
21911        .await
21912        .downcast::<Editor>()
21913        .unwrap();
21914    pane_1
21915        .update_in(cx, |pane, window, cx| {
21916            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21917        })
21918        .await
21919        .unwrap();
21920    drop(editor_1);
21921    pane_1.update(cx, |pane, cx| {
21922        pane.active_item()
21923            .unwrap()
21924            .downcast::<Editor>()
21925            .unwrap()
21926            .update(cx, |editor, cx| {
21927                assert_eq!(
21928                    editor.display_text(cx),
21929                    lib_text,
21930                    "Other file should be open and active",
21931                );
21932            });
21933        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21934    });
21935
21936    let _other_editor_2 = workspace
21937        .update_in(cx, |workspace, window, cx| {
21938            workspace.open_path(
21939                (worktree_id, "lib.rs"),
21940                Some(pane_2.downgrade()),
21941                true,
21942                window,
21943                cx,
21944            )
21945        })
21946        .unwrap()
21947        .await
21948        .downcast::<Editor>()
21949        .unwrap();
21950    pane_2
21951        .update_in(cx, |pane, window, cx| {
21952            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21953        })
21954        .await
21955        .unwrap();
21956    drop(editor_2);
21957    pane_2.update(cx, |pane, cx| {
21958        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21959        open_editor.update(cx, |editor, cx| {
21960            assert_eq!(
21961                editor.display_text(cx),
21962                lib_text,
21963                "Other file should be open and active in another panel too",
21964            );
21965        });
21966        assert_eq!(
21967            pane.items().count(),
21968            1,
21969            "No other editors should be open in another pane",
21970        );
21971    });
21972
21973    let _editor_1_reopened = workspace
21974        .update_in(cx, |workspace, window, cx| {
21975            workspace.open_path(
21976                (worktree_id, "main.rs"),
21977                Some(pane_1.downgrade()),
21978                true,
21979                window,
21980                cx,
21981            )
21982        })
21983        .unwrap()
21984        .await
21985        .downcast::<Editor>()
21986        .unwrap();
21987    let _editor_2_reopened = workspace
21988        .update_in(cx, |workspace, window, cx| {
21989            workspace.open_path(
21990                (worktree_id, "main.rs"),
21991                Some(pane_2.downgrade()),
21992                true,
21993                window,
21994                cx,
21995            )
21996        })
21997        .unwrap()
21998        .await
21999        .downcast::<Editor>()
22000        .unwrap();
22001    pane_1.update(cx, |pane, cx| {
22002        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22003        open_editor.update(cx, |editor, cx| {
22004            assert_eq!(
22005                editor.display_text(cx),
22006                main_text,
22007                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22008            );
22009            assert_eq!(
22010                editor
22011                    .selections
22012                    .all::<Point>(cx)
22013                    .into_iter()
22014                    .map(|s| s.range())
22015                    .collect::<Vec<_>>(),
22016                expected_ranges,
22017                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22018            );
22019        })
22020    });
22021    pane_2.update(cx, |pane, cx| {
22022        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22023        open_editor.update(cx, |editor, cx| {
22024            assert_eq!(
22025                editor.display_text(cx),
22026                r#"fn main() {
22027⋯rintln!("1");
22028⋯intln!("2");
22029⋯ntln!("3");
22030println!("4");
22031println!("5");
22032}"#,
22033                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22034            );
22035            assert_eq!(
22036                editor
22037                    .selections
22038                    .all::<Point>(cx)
22039                    .into_iter()
22040                    .map(|s| s.range())
22041                    .collect::<Vec<_>>(),
22042                vec![Point::zero()..Point::zero()],
22043                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22044            );
22045        })
22046    });
22047}
22048
22049#[gpui::test]
22050async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22051    init_test(cx, |_| {});
22052
22053    let fs = FakeFs::new(cx.executor());
22054    let main_text = r#"fn main() {
22055println!("1");
22056println!("2");
22057println!("3");
22058println!("4");
22059println!("5");
22060}"#;
22061    let lib_text = "mod foo {}";
22062    fs.insert_tree(
22063        path!("/a"),
22064        json!({
22065            "lib.rs": lib_text,
22066            "main.rs": main_text,
22067        }),
22068    )
22069    .await;
22070
22071    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22072    let (workspace, cx) =
22073        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22074    let worktree_id = workspace.update(cx, |workspace, cx| {
22075        workspace.project().update(cx, |project, cx| {
22076            project.worktrees(cx).next().unwrap().read(cx).id()
22077        })
22078    });
22079
22080    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22081    let editor = workspace
22082        .update_in(cx, |workspace, window, cx| {
22083            workspace.open_path(
22084                (worktree_id, "main.rs"),
22085                Some(pane.downgrade()),
22086                true,
22087                window,
22088                cx,
22089            )
22090        })
22091        .unwrap()
22092        .await
22093        .downcast::<Editor>()
22094        .unwrap();
22095    pane.update(cx, |pane, cx| {
22096        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22097        open_editor.update(cx, |editor, cx| {
22098            assert_eq!(
22099                editor.display_text(cx),
22100                main_text,
22101                "Original main.rs text on initial open",
22102            );
22103        })
22104    });
22105    editor.update_in(cx, |editor, window, cx| {
22106        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22107    });
22108
22109    cx.update_global(|store: &mut SettingsStore, cx| {
22110        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22111            s.restore_on_file_reopen = Some(false);
22112        });
22113    });
22114    editor.update_in(cx, |editor, window, cx| {
22115        editor.fold_ranges(
22116            vec![
22117                Point::new(1, 0)..Point::new(1, 1),
22118                Point::new(2, 0)..Point::new(2, 2),
22119                Point::new(3, 0)..Point::new(3, 3),
22120            ],
22121            false,
22122            window,
22123            cx,
22124        );
22125    });
22126    pane.update_in(cx, |pane, window, cx| {
22127        pane.close_all_items(&CloseAllItems::default(), window, cx)
22128    })
22129    .await
22130    .unwrap();
22131    pane.update(cx, |pane, _| {
22132        assert!(pane.active_item().is_none());
22133    });
22134    cx.update_global(|store: &mut SettingsStore, cx| {
22135        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22136            s.restore_on_file_reopen = Some(true);
22137        });
22138    });
22139
22140    let _editor_reopened = workspace
22141        .update_in(cx, |workspace, window, cx| {
22142            workspace.open_path(
22143                (worktree_id, "main.rs"),
22144                Some(pane.downgrade()),
22145                true,
22146                window,
22147                cx,
22148            )
22149        })
22150        .unwrap()
22151        .await
22152        .downcast::<Editor>()
22153        .unwrap();
22154    pane.update(cx, |pane, cx| {
22155        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22156        open_editor.update(cx, |editor, cx| {
22157            assert_eq!(
22158                editor.display_text(cx),
22159                main_text,
22160                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22161            );
22162        })
22163    });
22164}
22165
22166#[gpui::test]
22167async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22168    struct EmptyModalView {
22169        focus_handle: gpui::FocusHandle,
22170    }
22171    impl EventEmitter<DismissEvent> for EmptyModalView {}
22172    impl Render for EmptyModalView {
22173        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22174            div()
22175        }
22176    }
22177    impl Focusable for EmptyModalView {
22178        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22179            self.focus_handle.clone()
22180        }
22181    }
22182    impl workspace::ModalView for EmptyModalView {}
22183    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22184        EmptyModalView {
22185            focus_handle: cx.focus_handle(),
22186        }
22187    }
22188
22189    init_test(cx, |_| {});
22190
22191    let fs = FakeFs::new(cx.executor());
22192    let project = Project::test(fs, [], cx).await;
22193    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22194    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22195    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22196    let editor = cx.new_window_entity(|window, cx| {
22197        Editor::new(
22198            EditorMode::full(),
22199            buffer,
22200            Some(project.clone()),
22201            window,
22202            cx,
22203        )
22204    });
22205    workspace
22206        .update(cx, |workspace, window, cx| {
22207            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22208        })
22209        .unwrap();
22210    editor.update_in(cx, |editor, window, cx| {
22211        editor.open_context_menu(&OpenContextMenu, window, cx);
22212        assert!(editor.mouse_context_menu.is_some());
22213    });
22214    workspace
22215        .update(cx, |workspace, window, cx| {
22216            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22217        })
22218        .unwrap();
22219    cx.read(|cx| {
22220        assert!(editor.read(cx).mouse_context_menu.is_none());
22221    });
22222}
22223
22224#[gpui::test]
22225async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22226    init_test(cx, |_| {});
22227
22228    let fs = FakeFs::new(cx.executor());
22229    fs.insert_file(path!("/file.html"), Default::default())
22230        .await;
22231
22232    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22233
22234    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22235    let html_language = Arc::new(Language::new(
22236        LanguageConfig {
22237            name: "HTML".into(),
22238            matcher: LanguageMatcher {
22239                path_suffixes: vec!["html".to_string()],
22240                ..LanguageMatcher::default()
22241            },
22242            brackets: BracketPairConfig {
22243                pairs: vec![BracketPair {
22244                    start: "<".into(),
22245                    end: ">".into(),
22246                    close: true,
22247                    ..Default::default()
22248                }],
22249                ..Default::default()
22250            },
22251            ..Default::default()
22252        },
22253        Some(tree_sitter_html::LANGUAGE.into()),
22254    ));
22255    language_registry.add(html_language);
22256    let mut fake_servers = language_registry.register_fake_lsp(
22257        "HTML",
22258        FakeLspAdapter {
22259            capabilities: lsp::ServerCapabilities {
22260                completion_provider: Some(lsp::CompletionOptions {
22261                    resolve_provider: Some(true),
22262                    ..Default::default()
22263                }),
22264                ..Default::default()
22265            },
22266            ..Default::default()
22267        },
22268    );
22269
22270    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22271    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22272
22273    let worktree_id = workspace
22274        .update(cx, |workspace, _window, cx| {
22275            workspace.project().update(cx, |project, cx| {
22276                project.worktrees(cx).next().unwrap().read(cx).id()
22277            })
22278        })
22279        .unwrap();
22280    project
22281        .update(cx, |project, cx| {
22282            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22283        })
22284        .await
22285        .unwrap();
22286    let editor = workspace
22287        .update(cx, |workspace, window, cx| {
22288            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22289        })
22290        .unwrap()
22291        .await
22292        .unwrap()
22293        .downcast::<Editor>()
22294        .unwrap();
22295
22296    let fake_server = fake_servers.next().await.unwrap();
22297    editor.update_in(cx, |editor, window, cx| {
22298        editor.set_text("<ad></ad>", window, cx);
22299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22300            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22301        });
22302        let Some((buffer, _)) = editor
22303            .buffer
22304            .read(cx)
22305            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22306        else {
22307            panic!("Failed to get buffer for selection position");
22308        };
22309        let buffer = buffer.read(cx);
22310        let buffer_id = buffer.remote_id();
22311        let opening_range =
22312            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22313        let closing_range =
22314            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22315        let mut linked_ranges = HashMap::default();
22316        linked_ranges.insert(
22317            buffer_id,
22318            vec![(opening_range.clone(), vec![closing_range.clone()])],
22319        );
22320        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22321    });
22322    let mut completion_handle =
22323        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22324            Ok(Some(lsp::CompletionResponse::Array(vec![
22325                lsp::CompletionItem {
22326                    label: "head".to_string(),
22327                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22328                        lsp::InsertReplaceEdit {
22329                            new_text: "head".to_string(),
22330                            insert: lsp::Range::new(
22331                                lsp::Position::new(0, 1),
22332                                lsp::Position::new(0, 3),
22333                            ),
22334                            replace: lsp::Range::new(
22335                                lsp::Position::new(0, 1),
22336                                lsp::Position::new(0, 3),
22337                            ),
22338                        },
22339                    )),
22340                    ..Default::default()
22341                },
22342            ])))
22343        });
22344    editor.update_in(cx, |editor, window, cx| {
22345        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22346    });
22347    cx.run_until_parked();
22348    completion_handle.next().await.unwrap();
22349    editor.update(cx, |editor, _| {
22350        assert!(
22351            editor.context_menu_visible(),
22352            "Completion menu should be visible"
22353        );
22354    });
22355    editor.update_in(cx, |editor, window, cx| {
22356        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22357    });
22358    cx.executor().run_until_parked();
22359    editor.update(cx, |editor, cx| {
22360        assert_eq!(editor.text(cx), "<head></head>");
22361    });
22362}
22363
22364#[gpui::test]
22365async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22366    init_test(cx, |_| {});
22367
22368    let fs = FakeFs::new(cx.executor());
22369    fs.insert_tree(
22370        path!("/root"),
22371        json!({
22372            "a": {
22373                "main.rs": "fn main() {}",
22374            },
22375            "foo": {
22376                "bar": {
22377                    "external_file.rs": "pub mod external {}",
22378                }
22379            }
22380        }),
22381    )
22382    .await;
22383
22384    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22385    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22386    language_registry.add(rust_lang());
22387    let _fake_servers = language_registry.register_fake_lsp(
22388        "Rust",
22389        FakeLspAdapter {
22390            ..FakeLspAdapter::default()
22391        },
22392    );
22393    let (workspace, cx) =
22394        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22395    let worktree_id = workspace.update(cx, |workspace, cx| {
22396        workspace.project().update(cx, |project, cx| {
22397            project.worktrees(cx).next().unwrap().read(cx).id()
22398        })
22399    });
22400
22401    let assert_language_servers_count =
22402        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22403            project.update(cx, |project, cx| {
22404                let current = project
22405                    .lsp_store()
22406                    .read(cx)
22407                    .as_local()
22408                    .unwrap()
22409                    .language_servers
22410                    .len();
22411                assert_eq!(expected, current, "{context}");
22412            });
22413        };
22414
22415    assert_language_servers_count(
22416        0,
22417        "No servers should be running before any file is open",
22418        cx,
22419    );
22420    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22421    let main_editor = workspace
22422        .update_in(cx, |workspace, window, cx| {
22423            workspace.open_path(
22424                (worktree_id, "main.rs"),
22425                Some(pane.downgrade()),
22426                true,
22427                window,
22428                cx,
22429            )
22430        })
22431        .unwrap()
22432        .await
22433        .downcast::<Editor>()
22434        .unwrap();
22435    pane.update(cx, |pane, cx| {
22436        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22437        open_editor.update(cx, |editor, cx| {
22438            assert_eq!(
22439                editor.display_text(cx),
22440                "fn main() {}",
22441                "Original main.rs text on initial open",
22442            );
22443        });
22444        assert_eq!(open_editor, main_editor);
22445    });
22446    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22447
22448    let external_editor = workspace
22449        .update_in(cx, |workspace, window, cx| {
22450            workspace.open_abs_path(
22451                PathBuf::from("/root/foo/bar/external_file.rs"),
22452                OpenOptions::default(),
22453                window,
22454                cx,
22455            )
22456        })
22457        .await
22458        .expect("opening external file")
22459        .downcast::<Editor>()
22460        .expect("downcasted external file's open element to editor");
22461    pane.update(cx, |pane, cx| {
22462        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22463        open_editor.update(cx, |editor, cx| {
22464            assert_eq!(
22465                editor.display_text(cx),
22466                "pub mod external {}",
22467                "External file is open now",
22468            );
22469        });
22470        assert_eq!(open_editor, external_editor);
22471    });
22472    assert_language_servers_count(
22473        1,
22474        "Second, external, *.rs file should join the existing server",
22475        cx,
22476    );
22477
22478    pane.update_in(cx, |pane, window, cx| {
22479        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22480    })
22481    .await
22482    .unwrap();
22483    pane.update_in(cx, |pane, window, cx| {
22484        pane.navigate_backward(window, cx);
22485    });
22486    cx.run_until_parked();
22487    pane.update(cx, |pane, cx| {
22488        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22489        open_editor.update(cx, |editor, cx| {
22490            assert_eq!(
22491                editor.display_text(cx),
22492                "pub mod external {}",
22493                "External file is open now",
22494            );
22495        });
22496    });
22497    assert_language_servers_count(
22498        1,
22499        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22500        cx,
22501    );
22502
22503    cx.update(|_, cx| {
22504        workspace::reload(cx);
22505    });
22506    assert_language_servers_count(
22507        1,
22508        "After reloading the worktree with local and external files opened, only one project should be started",
22509        cx,
22510    );
22511}
22512
22513#[gpui::test]
22514async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22515    init_test(cx, |_| {});
22516
22517    let mut cx = EditorTestContext::new(cx).await;
22518    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22519    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22520
22521    // test cursor move to start of each line on tab
22522    // for `if`, `elif`, `else`, `while`, `with` and `for`
22523    cx.set_state(indoc! {"
22524        def main():
22525        ˇ    for item in items:
22526        ˇ        while item.active:
22527        ˇ            if item.value > 10:
22528        ˇ                continue
22529        ˇ            elif item.value < 0:
22530        ˇ                break
22531        ˇ            else:
22532        ˇ                with item.context() as ctx:
22533        ˇ                    yield count
22534        ˇ        else:
22535        ˇ            log('while else')
22536        ˇ    else:
22537        ˇ        log('for else')
22538    "});
22539    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22540    cx.assert_editor_state(indoc! {"
22541        def main():
22542            ˇfor item in items:
22543                ˇwhile item.active:
22544                    ˇif item.value > 10:
22545                        ˇcontinue
22546                    ˇelif item.value < 0:
22547                        ˇbreak
22548                    ˇelse:
22549                        ˇwith item.context() as ctx:
22550                            ˇyield count
22551                ˇelse:
22552                    ˇlog('while else')
22553            ˇelse:
22554                ˇlog('for else')
22555    "});
22556    // test relative indent is preserved when tab
22557    // for `if`, `elif`, `else`, `while`, `with` and `for`
22558    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22559    cx.assert_editor_state(indoc! {"
22560        def main():
22561                ˇfor item in items:
22562                    ˇwhile item.active:
22563                        ˇif item.value > 10:
22564                            ˇcontinue
22565                        ˇelif item.value < 0:
22566                            ˇbreak
22567                        ˇelse:
22568                            ˇwith item.context() as ctx:
22569                                ˇyield count
22570                    ˇelse:
22571                        ˇlog('while else')
22572                ˇelse:
22573                    ˇlog('for else')
22574    "});
22575
22576    // test cursor move to start of each line on tab
22577    // for `try`, `except`, `else`, `finally`, `match` and `def`
22578    cx.set_state(indoc! {"
22579        def main():
22580        ˇ    try:
22581        ˇ        fetch()
22582        ˇ    except ValueError:
22583        ˇ        handle_error()
22584        ˇ    else:
22585        ˇ        match value:
22586        ˇ            case _:
22587        ˇ    finally:
22588        ˇ        def status():
22589        ˇ            return 0
22590    "});
22591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22592    cx.assert_editor_state(indoc! {"
22593        def main():
22594            ˇtry:
22595                ˇfetch()
22596            ˇexcept ValueError:
22597                ˇhandle_error()
22598            ˇelse:
22599                ˇmatch value:
22600                    ˇcase _:
22601            ˇfinally:
22602                ˇdef status():
22603                    ˇreturn 0
22604    "});
22605    // test relative indent is preserved when tab
22606    // for `try`, `except`, `else`, `finally`, `match` and `def`
22607    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22608    cx.assert_editor_state(indoc! {"
22609        def main():
22610                ˇtry:
22611                    ˇfetch()
22612                ˇexcept ValueError:
22613                    ˇhandle_error()
22614                ˇelse:
22615                    ˇmatch value:
22616                        ˇcase _:
22617                ˇfinally:
22618                    ˇdef status():
22619                        ˇreturn 0
22620    "});
22621}
22622
22623#[gpui::test]
22624async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22625    init_test(cx, |_| {});
22626
22627    let mut cx = EditorTestContext::new(cx).await;
22628    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22629    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22630
22631    // test `else` auto outdents when typed inside `if` block
22632    cx.set_state(indoc! {"
22633        def main():
22634            if i == 2:
22635                return
22636                ˇ
22637    "});
22638    cx.update_editor(|editor, window, cx| {
22639        editor.handle_input("else:", window, cx);
22640    });
22641    cx.assert_editor_state(indoc! {"
22642        def main():
22643            if i == 2:
22644                return
22645            else:ˇ
22646    "});
22647
22648    // test `except` auto outdents when typed inside `try` block
22649    cx.set_state(indoc! {"
22650        def main():
22651            try:
22652                i = 2
22653                ˇ
22654    "});
22655    cx.update_editor(|editor, window, cx| {
22656        editor.handle_input("except:", window, cx);
22657    });
22658    cx.assert_editor_state(indoc! {"
22659        def main():
22660            try:
22661                i = 2
22662            except:ˇ
22663    "});
22664
22665    // test `else` auto outdents when typed inside `except` block
22666    cx.set_state(indoc! {"
22667        def main():
22668            try:
22669                i = 2
22670            except:
22671                j = 2
22672                ˇ
22673    "});
22674    cx.update_editor(|editor, window, cx| {
22675        editor.handle_input("else:", window, cx);
22676    });
22677    cx.assert_editor_state(indoc! {"
22678        def main():
22679            try:
22680                i = 2
22681            except:
22682                j = 2
22683            else:ˇ
22684    "});
22685
22686    // test `finally` auto outdents when typed inside `else` block
22687    cx.set_state(indoc! {"
22688        def main():
22689            try:
22690                i = 2
22691            except:
22692                j = 2
22693            else:
22694                k = 2
22695                ˇ
22696    "});
22697    cx.update_editor(|editor, window, cx| {
22698        editor.handle_input("finally:", window, cx);
22699    });
22700    cx.assert_editor_state(indoc! {"
22701        def main():
22702            try:
22703                i = 2
22704            except:
22705                j = 2
22706            else:
22707                k = 2
22708            finally:ˇ
22709    "});
22710
22711    // test `else` does not outdents when typed inside `except` block right after for block
22712    cx.set_state(indoc! {"
22713        def main():
22714            try:
22715                i = 2
22716            except:
22717                for i in range(n):
22718                    pass
22719                ˇ
22720    "});
22721    cx.update_editor(|editor, window, cx| {
22722        editor.handle_input("else:", window, cx);
22723    });
22724    cx.assert_editor_state(indoc! {"
22725        def main():
22726            try:
22727                i = 2
22728            except:
22729                for i in range(n):
22730                    pass
22731                else:ˇ
22732    "});
22733
22734    // test `finally` auto outdents when typed inside `else` block right after for block
22735    cx.set_state(indoc! {"
22736        def main():
22737            try:
22738                i = 2
22739            except:
22740                j = 2
22741            else:
22742                for i in range(n):
22743                    pass
22744                ˇ
22745    "});
22746    cx.update_editor(|editor, window, cx| {
22747        editor.handle_input("finally:", window, cx);
22748    });
22749    cx.assert_editor_state(indoc! {"
22750        def main():
22751            try:
22752                i = 2
22753            except:
22754                j = 2
22755            else:
22756                for i in range(n):
22757                    pass
22758            finally:ˇ
22759    "});
22760
22761    // test `except` outdents to inner "try" block
22762    cx.set_state(indoc! {"
22763        def main():
22764            try:
22765                i = 2
22766                if i == 2:
22767                    try:
22768                        i = 3
22769                        ˇ
22770    "});
22771    cx.update_editor(|editor, window, cx| {
22772        editor.handle_input("except:", window, cx);
22773    });
22774    cx.assert_editor_state(indoc! {"
22775        def main():
22776            try:
22777                i = 2
22778                if i == 2:
22779                    try:
22780                        i = 3
22781                    except:ˇ
22782    "});
22783
22784    // test `except` outdents to outer "try" block
22785    cx.set_state(indoc! {"
22786        def main():
22787            try:
22788                i = 2
22789                if i == 2:
22790                    try:
22791                        i = 3
22792                ˇ
22793    "});
22794    cx.update_editor(|editor, window, cx| {
22795        editor.handle_input("except:", window, cx);
22796    });
22797    cx.assert_editor_state(indoc! {"
22798        def main():
22799            try:
22800                i = 2
22801                if i == 2:
22802                    try:
22803                        i = 3
22804            except:ˇ
22805    "});
22806
22807    // test `else` stays at correct indent when typed after `for` block
22808    cx.set_state(indoc! {"
22809        def main():
22810            for i in range(10):
22811                if i == 3:
22812                    break
22813            ˇ
22814    "});
22815    cx.update_editor(|editor, window, cx| {
22816        editor.handle_input("else:", window, cx);
22817    });
22818    cx.assert_editor_state(indoc! {"
22819        def main():
22820            for i in range(10):
22821                if i == 3:
22822                    break
22823            else:ˇ
22824    "});
22825
22826    // test does not outdent on typing after line with square brackets
22827    cx.set_state(indoc! {"
22828        def f() -> list[str]:
22829            ˇ
22830    "});
22831    cx.update_editor(|editor, window, cx| {
22832        editor.handle_input("a", window, cx);
22833    });
22834    cx.assert_editor_state(indoc! {"
22835        def f() -> list[str]:
2283622837    "});
22838
22839    // test does not outdent on typing : after case keyword
22840    cx.set_state(indoc! {"
22841        match 1:
22842            caseˇ
22843    "});
22844    cx.update_editor(|editor, window, cx| {
22845        editor.handle_input(":", window, cx);
22846    });
22847    cx.assert_editor_state(indoc! {"
22848        match 1:
22849            case:ˇ
22850    "});
22851}
22852
22853#[gpui::test]
22854async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22855    init_test(cx, |_| {});
22856    update_test_language_settings(cx, |settings| {
22857        settings.defaults.extend_comment_on_newline = Some(false);
22858    });
22859    let mut cx = EditorTestContext::new(cx).await;
22860    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22861    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22862
22863    // test correct indent after newline on comment
22864    cx.set_state(indoc! {"
22865        # COMMENT:ˇ
22866    "});
22867    cx.update_editor(|editor, window, cx| {
22868        editor.newline(&Newline, window, cx);
22869    });
22870    cx.assert_editor_state(indoc! {"
22871        # COMMENT:
22872        ˇ
22873    "});
22874
22875    // test correct indent after newline in brackets
22876    cx.set_state(indoc! {"
22877        {ˇ}
22878    "});
22879    cx.update_editor(|editor, window, cx| {
22880        editor.newline(&Newline, window, cx);
22881    });
22882    cx.run_until_parked();
22883    cx.assert_editor_state(indoc! {"
22884        {
22885            ˇ
22886        }
22887    "});
22888
22889    cx.set_state(indoc! {"
22890        (ˇ)
22891    "});
22892    cx.update_editor(|editor, window, cx| {
22893        editor.newline(&Newline, window, cx);
22894    });
22895    cx.run_until_parked();
22896    cx.assert_editor_state(indoc! {"
22897        (
22898            ˇ
22899        )
22900    "});
22901
22902    // do not indent after empty lists or dictionaries
22903    cx.set_state(indoc! {"
22904        a = []ˇ
22905    "});
22906    cx.update_editor(|editor, window, cx| {
22907        editor.newline(&Newline, window, cx);
22908    });
22909    cx.run_until_parked();
22910    cx.assert_editor_state(indoc! {"
22911        a = []
22912        ˇ
22913    "});
22914}
22915
22916#[gpui::test]
22917async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22918    init_test(cx, |_| {});
22919
22920    let mut cx = EditorTestContext::new(cx).await;
22921    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22922    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22923
22924    // test cursor move to start of each line on tab
22925    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22926    cx.set_state(indoc! {"
22927        function main() {
22928        ˇ    for item in $items; do
22929        ˇ        while [ -n \"$item\" ]; do
22930        ˇ            if [ \"$value\" -gt 10 ]; then
22931        ˇ                continue
22932        ˇ            elif [ \"$value\" -lt 0 ]; then
22933        ˇ                break
22934        ˇ            else
22935        ˇ                echo \"$item\"
22936        ˇ            fi
22937        ˇ        done
22938        ˇ    done
22939        ˇ}
22940    "});
22941    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22942    cx.assert_editor_state(indoc! {"
22943        function main() {
22944            ˇfor item in $items; do
22945                ˇwhile [ -n \"$item\" ]; do
22946                    ˇif [ \"$value\" -gt 10 ]; then
22947                        ˇcontinue
22948                    ˇelif [ \"$value\" -lt 0 ]; then
22949                        ˇbreak
22950                    ˇelse
22951                        ˇecho \"$item\"
22952                    ˇfi
22953                ˇdone
22954            ˇdone
22955        ˇ}
22956    "});
22957    // test relative indent is preserved when tab
22958    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22959    cx.assert_editor_state(indoc! {"
22960        function main() {
22961                ˇfor item in $items; do
22962                    ˇwhile [ -n \"$item\" ]; do
22963                        ˇif [ \"$value\" -gt 10 ]; then
22964                            ˇcontinue
22965                        ˇelif [ \"$value\" -lt 0 ]; then
22966                            ˇbreak
22967                        ˇelse
22968                            ˇecho \"$item\"
22969                        ˇfi
22970                    ˇdone
22971                ˇdone
22972            ˇ}
22973    "});
22974
22975    // test cursor move to start of each line on tab
22976    // for `case` statement with patterns
22977    cx.set_state(indoc! {"
22978        function handle() {
22979        ˇ    case \"$1\" in
22980        ˇ        start)
22981        ˇ            echo \"a\"
22982        ˇ            ;;
22983        ˇ        stop)
22984        ˇ            echo \"b\"
22985        ˇ            ;;
22986        ˇ        *)
22987        ˇ            echo \"c\"
22988        ˇ            ;;
22989        ˇ    esac
22990        ˇ}
22991    "});
22992    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22993    cx.assert_editor_state(indoc! {"
22994        function handle() {
22995            ˇcase \"$1\" in
22996                ˇstart)
22997                    ˇecho \"a\"
22998                    ˇ;;
22999                ˇstop)
23000                    ˇecho \"b\"
23001                    ˇ;;
23002                ˇ*)
23003                    ˇecho \"c\"
23004                    ˇ;;
23005            ˇesac
23006        ˇ}
23007    "});
23008}
23009
23010#[gpui::test]
23011async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23012    init_test(cx, |_| {});
23013
23014    let mut cx = EditorTestContext::new(cx).await;
23015    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23016    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23017
23018    // test indents on comment insert
23019    cx.set_state(indoc! {"
23020        function main() {
23021        ˇ    for item in $items; do
23022        ˇ        while [ -n \"$item\" ]; do
23023        ˇ            if [ \"$value\" -gt 10 ]; then
23024        ˇ                continue
23025        ˇ            elif [ \"$value\" -lt 0 ]; then
23026        ˇ                break
23027        ˇ            else
23028        ˇ                echo \"$item\"
23029        ˇ            fi
23030        ˇ        done
23031        ˇ    done
23032        ˇ}
23033    "});
23034    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23035    cx.assert_editor_state(indoc! {"
23036        function main() {
23037        #ˇ    for item in $items; do
23038        #ˇ        while [ -n \"$item\" ]; do
23039        #ˇ            if [ \"$value\" -gt 10 ]; then
23040        #ˇ                continue
23041        #ˇ            elif [ \"$value\" -lt 0 ]; then
23042        #ˇ                break
23043        #ˇ            else
23044        #ˇ                echo \"$item\"
23045        #ˇ            fi
23046        #ˇ        done
23047        #ˇ    done
23048        #ˇ}
23049    "});
23050}
23051
23052#[gpui::test]
23053async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23054    init_test(cx, |_| {});
23055
23056    let mut cx = EditorTestContext::new(cx).await;
23057    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23058    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23059
23060    // test `else` auto outdents when typed inside `if` block
23061    cx.set_state(indoc! {"
23062        if [ \"$1\" = \"test\" ]; then
23063            echo \"foo bar\"
23064            ˇ
23065    "});
23066    cx.update_editor(|editor, window, cx| {
23067        editor.handle_input("else", window, cx);
23068    });
23069    cx.assert_editor_state(indoc! {"
23070        if [ \"$1\" = \"test\" ]; then
23071            echo \"foo bar\"
23072        elseˇ
23073    "});
23074
23075    // test `elif` auto outdents when typed inside `if` block
23076    cx.set_state(indoc! {"
23077        if [ \"$1\" = \"test\" ]; then
23078            echo \"foo bar\"
23079            ˇ
23080    "});
23081    cx.update_editor(|editor, window, cx| {
23082        editor.handle_input("elif", window, cx);
23083    });
23084    cx.assert_editor_state(indoc! {"
23085        if [ \"$1\" = \"test\" ]; then
23086            echo \"foo bar\"
23087        elifˇ
23088    "});
23089
23090    // test `fi` auto outdents when typed inside `else` block
23091    cx.set_state(indoc! {"
23092        if [ \"$1\" = \"test\" ]; then
23093            echo \"foo bar\"
23094        else
23095            echo \"bar baz\"
23096            ˇ
23097    "});
23098    cx.update_editor(|editor, window, cx| {
23099        editor.handle_input("fi", window, cx);
23100    });
23101    cx.assert_editor_state(indoc! {"
23102        if [ \"$1\" = \"test\" ]; then
23103            echo \"foo bar\"
23104        else
23105            echo \"bar baz\"
23106        fiˇ
23107    "});
23108
23109    // test `done` auto outdents when typed inside `while` block
23110    cx.set_state(indoc! {"
23111        while read line; do
23112            echo \"$line\"
23113            ˇ
23114    "});
23115    cx.update_editor(|editor, window, cx| {
23116        editor.handle_input("done", window, cx);
23117    });
23118    cx.assert_editor_state(indoc! {"
23119        while read line; do
23120            echo \"$line\"
23121        doneˇ
23122    "});
23123
23124    // test `done` auto outdents when typed inside `for` block
23125    cx.set_state(indoc! {"
23126        for file in *.txt; do
23127            cat \"$file\"
23128            ˇ
23129    "});
23130    cx.update_editor(|editor, window, cx| {
23131        editor.handle_input("done", window, cx);
23132    });
23133    cx.assert_editor_state(indoc! {"
23134        for file in *.txt; do
23135            cat \"$file\"
23136        doneˇ
23137    "});
23138
23139    // test `esac` auto outdents when typed inside `case` block
23140    cx.set_state(indoc! {"
23141        case \"$1\" in
23142            start)
23143                echo \"foo bar\"
23144                ;;
23145            stop)
23146                echo \"bar baz\"
23147                ;;
23148            ˇ
23149    "});
23150    cx.update_editor(|editor, window, cx| {
23151        editor.handle_input("esac", window, cx);
23152    });
23153    cx.assert_editor_state(indoc! {"
23154        case \"$1\" in
23155            start)
23156                echo \"foo bar\"
23157                ;;
23158            stop)
23159                echo \"bar baz\"
23160                ;;
23161        esacˇ
23162    "});
23163
23164    // test `*)` auto outdents when typed inside `case` block
23165    cx.set_state(indoc! {"
23166        case \"$1\" in
23167            start)
23168                echo \"foo bar\"
23169                ;;
23170                ˇ
23171    "});
23172    cx.update_editor(|editor, window, cx| {
23173        editor.handle_input("*)", window, cx);
23174    });
23175    cx.assert_editor_state(indoc! {"
23176        case \"$1\" in
23177            start)
23178                echo \"foo bar\"
23179                ;;
23180            *)ˇ
23181    "});
23182
23183    // test `fi` outdents to correct level with nested if blocks
23184    cx.set_state(indoc! {"
23185        if [ \"$1\" = \"test\" ]; then
23186            echo \"outer if\"
23187            if [ \"$2\" = \"debug\" ]; then
23188                echo \"inner if\"
23189                ˇ
23190    "});
23191    cx.update_editor(|editor, window, cx| {
23192        editor.handle_input("fi", window, cx);
23193    });
23194    cx.assert_editor_state(indoc! {"
23195        if [ \"$1\" = \"test\" ]; then
23196            echo \"outer if\"
23197            if [ \"$2\" = \"debug\" ]; then
23198                echo \"inner if\"
23199            fiˇ
23200    "});
23201}
23202
23203#[gpui::test]
23204async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23205    init_test(cx, |_| {});
23206    update_test_language_settings(cx, |settings| {
23207        settings.defaults.extend_comment_on_newline = Some(false);
23208    });
23209    let mut cx = EditorTestContext::new(cx).await;
23210    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23211    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23212
23213    // test correct indent after newline on comment
23214    cx.set_state(indoc! {"
23215        # COMMENT:ˇ
23216    "});
23217    cx.update_editor(|editor, window, cx| {
23218        editor.newline(&Newline, window, cx);
23219    });
23220    cx.assert_editor_state(indoc! {"
23221        # COMMENT:
23222        ˇ
23223    "});
23224
23225    // test correct indent after newline after `then`
23226    cx.set_state(indoc! {"
23227
23228        if [ \"$1\" = \"test\" ]; thenˇ
23229    "});
23230    cx.update_editor(|editor, window, cx| {
23231        editor.newline(&Newline, window, cx);
23232    });
23233    cx.run_until_parked();
23234    cx.assert_editor_state(indoc! {"
23235
23236        if [ \"$1\" = \"test\" ]; then
23237            ˇ
23238    "});
23239
23240    // test correct indent after newline after `else`
23241    cx.set_state(indoc! {"
23242        if [ \"$1\" = \"test\" ]; then
23243        elseˇ
23244    "});
23245    cx.update_editor(|editor, window, cx| {
23246        editor.newline(&Newline, window, cx);
23247    });
23248    cx.run_until_parked();
23249    cx.assert_editor_state(indoc! {"
23250        if [ \"$1\" = \"test\" ]; then
23251        else
23252            ˇ
23253    "});
23254
23255    // test correct indent after newline after `elif`
23256    cx.set_state(indoc! {"
23257        if [ \"$1\" = \"test\" ]; then
23258        elifˇ
23259    "});
23260    cx.update_editor(|editor, window, cx| {
23261        editor.newline(&Newline, window, cx);
23262    });
23263    cx.run_until_parked();
23264    cx.assert_editor_state(indoc! {"
23265        if [ \"$1\" = \"test\" ]; then
23266        elif
23267            ˇ
23268    "});
23269
23270    // test correct indent after newline after `do`
23271    cx.set_state(indoc! {"
23272        for file in *.txt; doˇ
23273    "});
23274    cx.update_editor(|editor, window, cx| {
23275        editor.newline(&Newline, window, cx);
23276    });
23277    cx.run_until_parked();
23278    cx.assert_editor_state(indoc! {"
23279        for file in *.txt; do
23280            ˇ
23281    "});
23282
23283    // test correct indent after newline after case pattern
23284    cx.set_state(indoc! {"
23285        case \"$1\" in
23286            start)ˇ
23287    "});
23288    cx.update_editor(|editor, window, cx| {
23289        editor.newline(&Newline, window, cx);
23290    });
23291    cx.run_until_parked();
23292    cx.assert_editor_state(indoc! {"
23293        case \"$1\" in
23294            start)
23295                ˇ
23296    "});
23297
23298    // test correct indent after newline after case pattern
23299    cx.set_state(indoc! {"
23300        case \"$1\" in
23301            start)
23302                ;;
23303            *)ˇ
23304    "});
23305    cx.update_editor(|editor, window, cx| {
23306        editor.newline(&Newline, window, cx);
23307    });
23308    cx.run_until_parked();
23309    cx.assert_editor_state(indoc! {"
23310        case \"$1\" in
23311            start)
23312                ;;
23313            *)
23314                ˇ
23315    "});
23316
23317    // test correct indent after newline after function opening brace
23318    cx.set_state(indoc! {"
23319        function test() {ˇ}
23320    "});
23321    cx.update_editor(|editor, window, cx| {
23322        editor.newline(&Newline, window, cx);
23323    });
23324    cx.run_until_parked();
23325    cx.assert_editor_state(indoc! {"
23326        function test() {
23327            ˇ
23328        }
23329    "});
23330
23331    // test no extra indent after semicolon on same line
23332    cx.set_state(indoc! {"
23333        echo \"test\"23334    "});
23335    cx.update_editor(|editor, window, cx| {
23336        editor.newline(&Newline, window, cx);
23337    });
23338    cx.run_until_parked();
23339    cx.assert_editor_state(indoc! {"
23340        echo \"test\";
23341        ˇ
23342    "});
23343}
23344
23345fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23346    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23347    point..point
23348}
23349
23350#[track_caller]
23351fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23352    let (text, ranges) = marked_text_ranges(marked_text, true);
23353    assert_eq!(editor.text(cx), text);
23354    assert_eq!(
23355        editor.selections.ranges(cx),
23356        ranges,
23357        "Assert selections are {}",
23358        marked_text
23359    );
23360}
23361
23362pub fn handle_signature_help_request(
23363    cx: &mut EditorLspTestContext,
23364    mocked_response: lsp::SignatureHelp,
23365) -> impl Future<Output = ()> + use<> {
23366    let mut request =
23367        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23368            let mocked_response = mocked_response.clone();
23369            async move { Ok(Some(mocked_response)) }
23370        });
23371
23372    async move {
23373        request.next().await;
23374    }
23375}
23376
23377#[track_caller]
23378pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23379    cx.update_editor(|editor, _, _| {
23380        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23381            let entries = menu.entries.borrow();
23382            let entries = entries
23383                .iter()
23384                .map(|entry| entry.string.as_str())
23385                .collect::<Vec<_>>();
23386            assert_eq!(entries, expected);
23387        } else {
23388            panic!("Expected completions menu");
23389        }
23390    });
23391}
23392
23393/// Handle completion request passing a marked string specifying where the completion
23394/// should be triggered from using '|' character, what range should be replaced, and what completions
23395/// should be returned using '<' and '>' to delimit the range.
23396///
23397/// Also see `handle_completion_request_with_insert_and_replace`.
23398#[track_caller]
23399pub fn handle_completion_request(
23400    marked_string: &str,
23401    completions: Vec<&'static str>,
23402    is_incomplete: bool,
23403    counter: Arc<AtomicUsize>,
23404    cx: &mut EditorLspTestContext,
23405) -> impl Future<Output = ()> {
23406    let complete_from_marker: TextRangeMarker = '|'.into();
23407    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23408    let (_, mut marked_ranges) = marked_text_ranges_by(
23409        marked_string,
23410        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23411    );
23412
23413    let complete_from_position =
23414        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23415    let replace_range =
23416        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23417
23418    let mut request =
23419        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23420            let completions = completions.clone();
23421            counter.fetch_add(1, atomic::Ordering::Release);
23422            async move {
23423                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23424                assert_eq!(
23425                    params.text_document_position.position,
23426                    complete_from_position
23427                );
23428                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23429                    is_incomplete: is_incomplete,
23430                    item_defaults: None,
23431                    items: completions
23432                        .iter()
23433                        .map(|completion_text| lsp::CompletionItem {
23434                            label: completion_text.to_string(),
23435                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23436                                range: replace_range,
23437                                new_text: completion_text.to_string(),
23438                            })),
23439                            ..Default::default()
23440                        })
23441                        .collect(),
23442                })))
23443            }
23444        });
23445
23446    async move {
23447        request.next().await;
23448    }
23449}
23450
23451/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23452/// given instead, which also contains an `insert` range.
23453///
23454/// This function uses markers to define ranges:
23455/// - `|` marks the cursor position
23456/// - `<>` marks the replace range
23457/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23458pub fn handle_completion_request_with_insert_and_replace(
23459    cx: &mut EditorLspTestContext,
23460    marked_string: &str,
23461    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23462    counter: Arc<AtomicUsize>,
23463) -> impl Future<Output = ()> {
23464    let complete_from_marker: TextRangeMarker = '|'.into();
23465    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23466    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23467
23468    let (_, mut marked_ranges) = marked_text_ranges_by(
23469        marked_string,
23470        vec![
23471            complete_from_marker.clone(),
23472            replace_range_marker.clone(),
23473            insert_range_marker.clone(),
23474        ],
23475    );
23476
23477    let complete_from_position =
23478        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23479    let replace_range =
23480        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23481
23482    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23483        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23484        _ => lsp::Range {
23485            start: replace_range.start,
23486            end: complete_from_position,
23487        },
23488    };
23489
23490    let mut request =
23491        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23492            let completions = completions.clone();
23493            counter.fetch_add(1, atomic::Ordering::Release);
23494            async move {
23495                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23496                assert_eq!(
23497                    params.text_document_position.position, complete_from_position,
23498                    "marker `|` position doesn't match",
23499                );
23500                Ok(Some(lsp::CompletionResponse::Array(
23501                    completions
23502                        .iter()
23503                        .map(|(label, new_text)| lsp::CompletionItem {
23504                            label: label.to_string(),
23505                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23506                                lsp::InsertReplaceEdit {
23507                                    insert: insert_range,
23508                                    replace: replace_range,
23509                                    new_text: new_text.to_string(),
23510                                },
23511                            )),
23512                            ..Default::default()
23513                        })
23514                        .collect(),
23515                )))
23516            }
23517        });
23518
23519    async move {
23520        request.next().await;
23521    }
23522}
23523
23524fn handle_resolve_completion_request(
23525    cx: &mut EditorLspTestContext,
23526    edits: Option<Vec<(&'static str, &'static str)>>,
23527) -> impl Future<Output = ()> {
23528    let edits = edits.map(|edits| {
23529        edits
23530            .iter()
23531            .map(|(marked_string, new_text)| {
23532                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23533                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23534                lsp::TextEdit::new(replace_range, new_text.to_string())
23535            })
23536            .collect::<Vec<_>>()
23537    });
23538
23539    let mut request =
23540        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23541            let edits = edits.clone();
23542            async move {
23543                Ok(lsp::CompletionItem {
23544                    additional_text_edits: edits,
23545                    ..Default::default()
23546                })
23547            }
23548        });
23549
23550    async move {
23551        request.next().await;
23552    }
23553}
23554
23555pub(crate) fn update_test_language_settings(
23556    cx: &mut TestAppContext,
23557    f: impl Fn(&mut AllLanguageSettingsContent),
23558) {
23559    cx.update(|cx| {
23560        SettingsStore::update_global(cx, |store, cx| {
23561            store.update_user_settings::<AllLanguageSettings>(cx, f);
23562        });
23563    });
23564}
23565
23566pub(crate) fn update_test_project_settings(
23567    cx: &mut TestAppContext,
23568    f: impl Fn(&mut ProjectSettings),
23569) {
23570    cx.update(|cx| {
23571        SettingsStore::update_global(cx, |store, cx| {
23572            store.update_user_settings::<ProjectSettings>(cx, f);
23573        });
23574    });
23575}
23576
23577pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23578    cx.update(|cx| {
23579        assets::Assets.load_test_fonts(cx);
23580        let store = SettingsStore::test(cx);
23581        cx.set_global(store);
23582        theme::init(theme::LoadThemes::JustBase, cx);
23583        release_channel::init(SemanticVersion::default(), cx);
23584        client::init_settings(cx);
23585        language::init(cx);
23586        Project::init_settings(cx);
23587        workspace::init_settings(cx);
23588        crate::init(cx);
23589    });
23590    zlog::init_test();
23591    update_test_language_settings(cx, f);
23592}
23593
23594#[track_caller]
23595fn assert_hunk_revert(
23596    not_reverted_text_with_selections: &str,
23597    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23598    expected_reverted_text_with_selections: &str,
23599    base_text: &str,
23600    cx: &mut EditorLspTestContext,
23601) {
23602    cx.set_state(not_reverted_text_with_selections);
23603    cx.set_head_text(base_text);
23604    cx.executor().run_until_parked();
23605
23606    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23607        let snapshot = editor.snapshot(window, cx);
23608        let reverted_hunk_statuses = snapshot
23609            .buffer_snapshot
23610            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23611            .map(|hunk| hunk.status().kind)
23612            .collect::<Vec<_>>();
23613
23614        editor.git_restore(&Default::default(), window, cx);
23615        reverted_hunk_statuses
23616    });
23617    cx.executor().run_until_parked();
23618    cx.assert_editor_state(expected_reverted_text_with_selections);
23619    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23620}
23621
23622#[gpui::test(iterations = 10)]
23623async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23624    init_test(cx, |_| {});
23625
23626    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23627    let counter = diagnostic_requests.clone();
23628
23629    let fs = FakeFs::new(cx.executor());
23630    fs.insert_tree(
23631        path!("/a"),
23632        json!({
23633            "first.rs": "fn main() { let a = 5; }",
23634            "second.rs": "// Test file",
23635        }),
23636    )
23637    .await;
23638
23639    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23640    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23641    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23642
23643    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23644    language_registry.add(rust_lang());
23645    let mut fake_servers = language_registry.register_fake_lsp(
23646        "Rust",
23647        FakeLspAdapter {
23648            capabilities: lsp::ServerCapabilities {
23649                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23650                    lsp::DiagnosticOptions {
23651                        identifier: None,
23652                        inter_file_dependencies: true,
23653                        workspace_diagnostics: true,
23654                        work_done_progress_options: Default::default(),
23655                    },
23656                )),
23657                ..Default::default()
23658            },
23659            ..Default::default()
23660        },
23661    );
23662
23663    let editor = workspace
23664        .update(cx, |workspace, window, cx| {
23665            workspace.open_abs_path(
23666                PathBuf::from(path!("/a/first.rs")),
23667                OpenOptions::default(),
23668                window,
23669                cx,
23670            )
23671        })
23672        .unwrap()
23673        .await
23674        .unwrap()
23675        .downcast::<Editor>()
23676        .unwrap();
23677    let fake_server = fake_servers.next().await.unwrap();
23678    let server_id = fake_server.server.server_id();
23679    let mut first_request = fake_server
23680        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23681            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23682            let result_id = Some(new_result_id.to_string());
23683            assert_eq!(
23684                params.text_document.uri,
23685                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23686            );
23687            async move {
23688                Ok(lsp::DocumentDiagnosticReportResult::Report(
23689                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23690                        related_documents: None,
23691                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23692                            items: Vec::new(),
23693                            result_id,
23694                        },
23695                    }),
23696                ))
23697            }
23698        });
23699
23700    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23701        project.update(cx, |project, cx| {
23702            let buffer_id = editor
23703                .read(cx)
23704                .buffer()
23705                .read(cx)
23706                .as_singleton()
23707                .expect("created a singleton buffer")
23708                .read(cx)
23709                .remote_id();
23710            let buffer_result_id = project
23711                .lsp_store()
23712                .read(cx)
23713                .result_id(server_id, buffer_id, cx);
23714            assert_eq!(expected, buffer_result_id);
23715        });
23716    };
23717
23718    ensure_result_id(None, cx);
23719    cx.executor().advance_clock(Duration::from_millis(60));
23720    cx.executor().run_until_parked();
23721    assert_eq!(
23722        diagnostic_requests.load(atomic::Ordering::Acquire),
23723        1,
23724        "Opening file should trigger diagnostic request"
23725    );
23726    first_request
23727        .next()
23728        .await
23729        .expect("should have sent the first diagnostics pull request");
23730    ensure_result_id(Some("1".to_string()), cx);
23731
23732    // Editing should trigger diagnostics
23733    editor.update_in(cx, |editor, window, cx| {
23734        editor.handle_input("2", window, cx)
23735    });
23736    cx.executor().advance_clock(Duration::from_millis(60));
23737    cx.executor().run_until_parked();
23738    assert_eq!(
23739        diagnostic_requests.load(atomic::Ordering::Acquire),
23740        2,
23741        "Editing should trigger diagnostic request"
23742    );
23743    ensure_result_id(Some("2".to_string()), cx);
23744
23745    // Moving cursor should not trigger diagnostic request
23746    editor.update_in(cx, |editor, window, cx| {
23747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23748            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23749        });
23750    });
23751    cx.executor().advance_clock(Duration::from_millis(60));
23752    cx.executor().run_until_parked();
23753    assert_eq!(
23754        diagnostic_requests.load(atomic::Ordering::Acquire),
23755        2,
23756        "Cursor movement should not trigger diagnostic request"
23757    );
23758    ensure_result_id(Some("2".to_string()), cx);
23759    // Multiple rapid edits should be debounced
23760    for _ in 0..5 {
23761        editor.update_in(cx, |editor, window, cx| {
23762            editor.handle_input("x", window, cx)
23763        });
23764    }
23765    cx.executor().advance_clock(Duration::from_millis(60));
23766    cx.executor().run_until_parked();
23767
23768    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23769    assert!(
23770        final_requests <= 4,
23771        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23772    );
23773    ensure_result_id(Some(final_requests.to_string()), cx);
23774}
23775
23776#[gpui::test]
23777async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23778    // Regression test for issue #11671
23779    // Previously, adding a cursor after moving multiple cursors would reset
23780    // the cursor count instead of adding to the existing cursors.
23781    init_test(cx, |_| {});
23782    let mut cx = EditorTestContext::new(cx).await;
23783
23784    // Create a simple buffer with cursor at start
23785    cx.set_state(indoc! {"
23786        ˇaaaa
23787        bbbb
23788        cccc
23789        dddd
23790        eeee
23791        ffff
23792        gggg
23793        hhhh"});
23794
23795    // Add 2 cursors below (so we have 3 total)
23796    cx.update_editor(|editor, window, cx| {
23797        editor.add_selection_below(&Default::default(), window, cx);
23798        editor.add_selection_below(&Default::default(), window, cx);
23799    });
23800
23801    // Verify we have 3 cursors
23802    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23803    assert_eq!(
23804        initial_count, 3,
23805        "Should have 3 cursors after adding 2 below"
23806    );
23807
23808    // Move down one line
23809    cx.update_editor(|editor, window, cx| {
23810        editor.move_down(&MoveDown, window, cx);
23811    });
23812
23813    // Add another cursor below
23814    cx.update_editor(|editor, window, cx| {
23815        editor.add_selection_below(&Default::default(), window, cx);
23816    });
23817
23818    // Should now have 4 cursors (3 original + 1 new)
23819    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23820    assert_eq!(
23821        final_count, 4,
23822        "Should have 4 cursors after moving and adding another"
23823    );
23824}
23825
23826#[gpui::test(iterations = 10)]
23827async fn test_document_colors(cx: &mut TestAppContext) {
23828    let expected_color = Rgba {
23829        r: 0.33,
23830        g: 0.33,
23831        b: 0.33,
23832        a: 0.33,
23833    };
23834
23835    init_test(cx, |_| {});
23836
23837    let fs = FakeFs::new(cx.executor());
23838    fs.insert_tree(
23839        path!("/a"),
23840        json!({
23841            "first.rs": "fn main() { let a = 5; }",
23842        }),
23843    )
23844    .await;
23845
23846    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23847    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23848    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23849
23850    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23851    language_registry.add(rust_lang());
23852    let mut fake_servers = language_registry.register_fake_lsp(
23853        "Rust",
23854        FakeLspAdapter {
23855            capabilities: lsp::ServerCapabilities {
23856                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23857                ..lsp::ServerCapabilities::default()
23858            },
23859            name: "rust-analyzer",
23860            ..FakeLspAdapter::default()
23861        },
23862    );
23863    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23864        "Rust",
23865        FakeLspAdapter {
23866            capabilities: lsp::ServerCapabilities {
23867                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23868                ..lsp::ServerCapabilities::default()
23869            },
23870            name: "not-rust-analyzer",
23871            ..FakeLspAdapter::default()
23872        },
23873    );
23874
23875    let editor = workspace
23876        .update(cx, |workspace, window, cx| {
23877            workspace.open_abs_path(
23878                PathBuf::from(path!("/a/first.rs")),
23879                OpenOptions::default(),
23880                window,
23881                cx,
23882            )
23883        })
23884        .unwrap()
23885        .await
23886        .unwrap()
23887        .downcast::<Editor>()
23888        .unwrap();
23889    let fake_language_server = fake_servers.next().await.unwrap();
23890    let fake_language_server_without_capabilities =
23891        fake_servers_without_capabilities.next().await.unwrap();
23892    let requests_made = Arc::new(AtomicUsize::new(0));
23893    let closure_requests_made = Arc::clone(&requests_made);
23894    let mut color_request_handle = fake_language_server
23895        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23896            let requests_made = Arc::clone(&closure_requests_made);
23897            async move {
23898                assert_eq!(
23899                    params.text_document.uri,
23900                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23901                );
23902                requests_made.fetch_add(1, atomic::Ordering::Release);
23903                Ok(vec![
23904                    lsp::ColorInformation {
23905                        range: lsp::Range {
23906                            start: lsp::Position {
23907                                line: 0,
23908                                character: 0,
23909                            },
23910                            end: lsp::Position {
23911                                line: 0,
23912                                character: 1,
23913                            },
23914                        },
23915                        color: lsp::Color {
23916                            red: 0.33,
23917                            green: 0.33,
23918                            blue: 0.33,
23919                            alpha: 0.33,
23920                        },
23921                    },
23922                    lsp::ColorInformation {
23923                        range: lsp::Range {
23924                            start: lsp::Position {
23925                                line: 0,
23926                                character: 0,
23927                            },
23928                            end: lsp::Position {
23929                                line: 0,
23930                                character: 1,
23931                            },
23932                        },
23933                        color: lsp::Color {
23934                            red: 0.33,
23935                            green: 0.33,
23936                            blue: 0.33,
23937                            alpha: 0.33,
23938                        },
23939                    },
23940                ])
23941            }
23942        });
23943
23944    let _handle = fake_language_server_without_capabilities
23945        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23946            panic!("Should not be called");
23947        });
23948    cx.executor().advance_clock(Duration::from_millis(100));
23949    color_request_handle.next().await.unwrap();
23950    cx.run_until_parked();
23951    assert_eq!(
23952        1,
23953        requests_made.load(atomic::Ordering::Acquire),
23954        "Should query for colors once per editor open"
23955    );
23956    editor.update_in(cx, |editor, _, cx| {
23957        assert_eq!(
23958            vec![expected_color],
23959            extract_color_inlays(editor, cx),
23960            "Should have an initial inlay"
23961        );
23962    });
23963
23964    // opening another file in a split should not influence the LSP query counter
23965    workspace
23966        .update(cx, |workspace, window, cx| {
23967            assert_eq!(
23968                workspace.panes().len(),
23969                1,
23970                "Should have one pane with one editor"
23971            );
23972            workspace.move_item_to_pane_in_direction(
23973                &MoveItemToPaneInDirection {
23974                    direction: SplitDirection::Right,
23975                    focus: false,
23976                    clone: true,
23977                },
23978                window,
23979                cx,
23980            );
23981        })
23982        .unwrap();
23983    cx.run_until_parked();
23984    workspace
23985        .update(cx, |workspace, _, cx| {
23986            let panes = workspace.panes();
23987            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23988            for pane in panes {
23989                let editor = pane
23990                    .read(cx)
23991                    .active_item()
23992                    .and_then(|item| item.downcast::<Editor>())
23993                    .expect("Should have opened an editor in each split");
23994                let editor_file = editor
23995                    .read(cx)
23996                    .buffer()
23997                    .read(cx)
23998                    .as_singleton()
23999                    .expect("test deals with singleton buffers")
24000                    .read(cx)
24001                    .file()
24002                    .expect("test buffese should have a file")
24003                    .path();
24004                assert_eq!(
24005                    editor_file.as_ref(),
24006                    Path::new("first.rs"),
24007                    "Both editors should be opened for the same file"
24008                )
24009            }
24010        })
24011        .unwrap();
24012
24013    cx.executor().advance_clock(Duration::from_millis(500));
24014    let save = editor.update_in(cx, |editor, window, cx| {
24015        editor.move_to_end(&MoveToEnd, window, cx);
24016        editor.handle_input("dirty", window, cx);
24017        editor.save(
24018            SaveOptions {
24019                format: true,
24020                autosave: true,
24021            },
24022            project.clone(),
24023            window,
24024            cx,
24025        )
24026    });
24027    save.await.unwrap();
24028
24029    color_request_handle.next().await.unwrap();
24030    cx.run_until_parked();
24031    assert_eq!(
24032        3,
24033        requests_made.load(atomic::Ordering::Acquire),
24034        "Should query for colors once per save and once per formatting after save"
24035    );
24036
24037    drop(editor);
24038    let close = workspace
24039        .update(cx, |workspace, window, cx| {
24040            workspace.active_pane().update(cx, |pane, cx| {
24041                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24042            })
24043        })
24044        .unwrap();
24045    close.await.unwrap();
24046    let close = workspace
24047        .update(cx, |workspace, window, cx| {
24048            workspace.active_pane().update(cx, |pane, cx| {
24049                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24050            })
24051        })
24052        .unwrap();
24053    close.await.unwrap();
24054    assert_eq!(
24055        3,
24056        requests_made.load(atomic::Ordering::Acquire),
24057        "After saving and closing all editors, no extra requests should be made"
24058    );
24059    workspace
24060        .update(cx, |workspace, _, cx| {
24061            assert!(
24062                workspace.active_item(cx).is_none(),
24063                "Should close all editors"
24064            )
24065        })
24066        .unwrap();
24067
24068    workspace
24069        .update(cx, |workspace, window, cx| {
24070            workspace.active_pane().update(cx, |pane, cx| {
24071                pane.navigate_backward(window, cx);
24072            })
24073        })
24074        .unwrap();
24075    cx.executor().advance_clock(Duration::from_millis(100));
24076    cx.run_until_parked();
24077    let editor = workspace
24078        .update(cx, |workspace, _, cx| {
24079            workspace
24080                .active_item(cx)
24081                .expect("Should have reopened the editor again after navigating back")
24082                .downcast::<Editor>()
24083                .expect("Should be an editor")
24084        })
24085        .unwrap();
24086    color_request_handle.next().await.unwrap();
24087    assert_eq!(
24088        3,
24089        requests_made.load(atomic::Ordering::Acquire),
24090        "Cache should be reused on buffer close and reopen"
24091    );
24092    editor.update(cx, |editor, cx| {
24093        assert_eq!(
24094            vec![expected_color],
24095            extract_color_inlays(editor, cx),
24096            "Should have an initial inlay"
24097        );
24098    });
24099}
24100
24101#[gpui::test]
24102async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24103    init_test(cx, |_| {});
24104    let (editor, cx) = cx.add_window_view(Editor::single_line);
24105    editor.update_in(cx, |editor, window, cx| {
24106        editor.set_text("oops\n\nwow\n", window, cx)
24107    });
24108    cx.run_until_parked();
24109    editor.update(cx, |editor, cx| {
24110        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24111    });
24112    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24113    cx.run_until_parked();
24114    editor.update(cx, |editor, cx| {
24115        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24116    });
24117}
24118
24119#[track_caller]
24120fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24121    editor
24122        .all_inlays(cx)
24123        .into_iter()
24124        .filter_map(|inlay| inlay.get_color())
24125        .map(Rgba::from)
24126        .collect()
24127}