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();
   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(),
   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 =
19638                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
19639            snapshot
19640        })
19641        .unwrap();
19642
19643    let render_args = render_args.lock().take().unwrap();
19644    assert_eq!(render_args.row, MultiBufferRow(1));
19645    assert!(!render_args.folded);
19646    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19647
19648    cx.update_window(*editor, |_, window, cx| {
19649        (render_args.callback)(true, window, cx)
19650    })
19651    .unwrap();
19652    let snapshot = editor
19653        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19654        .unwrap();
19655    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19656
19657    cx.update_window(*editor, |_, window, cx| {
19658        (render_args.callback)(false, window, cx)
19659    })
19660    .unwrap();
19661    let snapshot = editor
19662        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19663        .unwrap();
19664    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19665}
19666
19667#[gpui::test]
19668async fn test_input_text(cx: &mut TestAppContext) {
19669    init_test(cx, |_| {});
19670    let mut cx = EditorTestContext::new(cx).await;
19671
19672    cx.set_state(
19673        &r#"ˇone
19674        two
19675
19676        three
19677        fourˇ
19678        five
19679
19680        siˇx"#
19681            .unindent(),
19682    );
19683
19684    cx.dispatch_action(HandleInput(String::new()));
19685    cx.assert_editor_state(
19686        &r#"ˇone
19687        two
19688
19689        three
19690        fourˇ
19691        five
19692
19693        siˇx"#
19694            .unindent(),
19695    );
19696
19697    cx.dispatch_action(HandleInput("AAAA".to_string()));
19698    cx.assert_editor_state(
19699        &r#"AAAAˇone
19700        two
19701
19702        three
19703        fourAAAAˇ
19704        five
19705
19706        siAAAAˇx"#
19707            .unindent(),
19708    );
19709}
19710
19711#[gpui::test]
19712async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19713    init_test(cx, |_| {});
19714
19715    let mut cx = EditorTestContext::new(cx).await;
19716    cx.set_state(
19717        r#"let foo = 1;
19718let foo = 2;
19719let foo = 3;
19720let fooˇ = 4;
19721let foo = 5;
19722let foo = 6;
19723let foo = 7;
19724let foo = 8;
19725let foo = 9;
19726let foo = 10;
19727let foo = 11;
19728let foo = 12;
19729let foo = 13;
19730let foo = 14;
19731let foo = 15;"#,
19732    );
19733
19734    cx.update_editor(|e, window, cx| {
19735        assert_eq!(
19736            e.next_scroll_position,
19737            NextScrollCursorCenterTopBottom::Center,
19738            "Default next scroll direction is center",
19739        );
19740
19741        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19742        assert_eq!(
19743            e.next_scroll_position,
19744            NextScrollCursorCenterTopBottom::Top,
19745            "After center, next scroll direction should be top",
19746        );
19747
19748        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19749        assert_eq!(
19750            e.next_scroll_position,
19751            NextScrollCursorCenterTopBottom::Bottom,
19752            "After top, next scroll direction should be bottom",
19753        );
19754
19755        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19756        assert_eq!(
19757            e.next_scroll_position,
19758            NextScrollCursorCenterTopBottom::Center,
19759            "After bottom, scrolling should start over",
19760        );
19761
19762        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19763        assert_eq!(
19764            e.next_scroll_position,
19765            NextScrollCursorCenterTopBottom::Top,
19766            "Scrolling continues if retriggered fast enough"
19767        );
19768    });
19769
19770    cx.executor()
19771        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19772    cx.executor().run_until_parked();
19773    cx.update_editor(|e, _, _| {
19774        assert_eq!(
19775            e.next_scroll_position,
19776            NextScrollCursorCenterTopBottom::Center,
19777            "If scrolling is not triggered fast enough, it should reset"
19778        );
19779    });
19780}
19781
19782#[gpui::test]
19783async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19784    init_test(cx, |_| {});
19785    let mut cx = EditorLspTestContext::new_rust(
19786        lsp::ServerCapabilities {
19787            definition_provider: Some(lsp::OneOf::Left(true)),
19788            references_provider: Some(lsp::OneOf::Left(true)),
19789            ..lsp::ServerCapabilities::default()
19790        },
19791        cx,
19792    )
19793    .await;
19794
19795    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19796        let go_to_definition = cx
19797            .lsp
19798            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19799                move |params, _| async move {
19800                    if empty_go_to_definition {
19801                        Ok(None)
19802                    } else {
19803                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19804                            uri: params.text_document_position_params.text_document.uri,
19805                            range: lsp::Range::new(
19806                                lsp::Position::new(4, 3),
19807                                lsp::Position::new(4, 6),
19808                            ),
19809                        })))
19810                    }
19811                },
19812            );
19813        let references = cx
19814            .lsp
19815            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19816                Ok(Some(vec![lsp::Location {
19817                    uri: params.text_document_position.text_document.uri,
19818                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19819                }]))
19820            });
19821        (go_to_definition, references)
19822    };
19823
19824    cx.set_state(
19825        &r#"fn one() {
19826            let mut a = ˇtwo();
19827        }
19828
19829        fn two() {}"#
19830            .unindent(),
19831    );
19832    set_up_lsp_handlers(false, &mut cx);
19833    let navigated = cx
19834        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19835        .await
19836        .expect("Failed to navigate to definition");
19837    assert_eq!(
19838        navigated,
19839        Navigated::Yes,
19840        "Should have navigated to definition from the GetDefinition response"
19841    );
19842    cx.assert_editor_state(
19843        &r#"fn one() {
19844            let mut a = two();
19845        }
19846
19847        fn «twoˇ»() {}"#
19848            .unindent(),
19849    );
19850
19851    let editors = cx.update_workspace(|workspace, _, cx| {
19852        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19853    });
19854    cx.update_editor(|_, _, test_editor_cx| {
19855        assert_eq!(
19856            editors.len(),
19857            1,
19858            "Initially, only one, test, editor should be open in the workspace"
19859        );
19860        assert_eq!(
19861            test_editor_cx.entity(),
19862            editors.last().expect("Asserted len is 1").clone()
19863        );
19864    });
19865
19866    set_up_lsp_handlers(true, &mut cx);
19867    let navigated = cx
19868        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19869        .await
19870        .expect("Failed to navigate to lookup references");
19871    assert_eq!(
19872        navigated,
19873        Navigated::Yes,
19874        "Should have navigated to references as a fallback after empty GoToDefinition response"
19875    );
19876    // We should not change the selections in the existing file,
19877    // if opening another milti buffer with the references
19878    cx.assert_editor_state(
19879        &r#"fn one() {
19880            let mut a = two();
19881        }
19882
19883        fn «twoˇ»() {}"#
19884            .unindent(),
19885    );
19886    let editors = cx.update_workspace(|workspace, _, cx| {
19887        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19888    });
19889    cx.update_editor(|_, _, test_editor_cx| {
19890        assert_eq!(
19891            editors.len(),
19892            2,
19893            "After falling back to references search, we open a new editor with the results"
19894        );
19895        let references_fallback_text = editors
19896            .into_iter()
19897            .find(|new_editor| *new_editor != test_editor_cx.entity())
19898            .expect("Should have one non-test editor now")
19899            .read(test_editor_cx)
19900            .text(test_editor_cx);
19901        assert_eq!(
19902            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19903            "Should use the range from the references response and not the GoToDefinition one"
19904        );
19905    });
19906}
19907
19908#[gpui::test]
19909async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19910    init_test(cx, |_| {});
19911    cx.update(|cx| {
19912        let mut editor_settings = EditorSettings::get_global(cx).clone();
19913        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19914        EditorSettings::override_global(editor_settings, cx);
19915    });
19916    let mut cx = EditorLspTestContext::new_rust(
19917        lsp::ServerCapabilities {
19918            definition_provider: Some(lsp::OneOf::Left(true)),
19919            references_provider: Some(lsp::OneOf::Left(true)),
19920            ..lsp::ServerCapabilities::default()
19921        },
19922        cx,
19923    )
19924    .await;
19925    let original_state = r#"fn one() {
19926        let mut a = ˇtwo();
19927    }
19928
19929    fn two() {}"#
19930        .unindent();
19931    cx.set_state(&original_state);
19932
19933    let mut go_to_definition = cx
19934        .lsp
19935        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19936            move |_, _| async move { Ok(None) },
19937        );
19938    let _references = cx
19939        .lsp
19940        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19941            panic!("Should not call for references with no go to definition fallback")
19942        });
19943
19944    let navigated = cx
19945        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19946        .await
19947        .expect("Failed to navigate to lookup references");
19948    go_to_definition
19949        .next()
19950        .await
19951        .expect("Should have called the go_to_definition handler");
19952
19953    assert_eq!(
19954        navigated,
19955        Navigated::No,
19956        "Should have navigated to references as a fallback after empty GoToDefinition response"
19957    );
19958    cx.assert_editor_state(&original_state);
19959    let editors = cx.update_workspace(|workspace, _, cx| {
19960        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19961    });
19962    cx.update_editor(|_, _, _| {
19963        assert_eq!(
19964            editors.len(),
19965            1,
19966            "After unsuccessful fallback, no other editor should have been opened"
19967        );
19968    });
19969}
19970
19971#[gpui::test]
19972async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19973    init_test(cx, |_| {});
19974
19975    let language = Arc::new(Language::new(
19976        LanguageConfig::default(),
19977        Some(tree_sitter_rust::LANGUAGE.into()),
19978    ));
19979
19980    let text = r#"
19981        #[cfg(test)]
19982        mod tests() {
19983            #[test]
19984            fn runnable_1() {
19985                let a = 1;
19986            }
19987
19988            #[test]
19989            fn runnable_2() {
19990                let a = 1;
19991                let b = 2;
19992            }
19993        }
19994    "#
19995    .unindent();
19996
19997    let fs = FakeFs::new(cx.executor());
19998    fs.insert_file("/file.rs", Default::default()).await;
19999
20000    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20001    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20002    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20003    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20004    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20005
20006    let editor = cx.new_window_entity(|window, cx| {
20007        Editor::new(
20008            EditorMode::full(),
20009            multi_buffer,
20010            Some(project.clone()),
20011            window,
20012            cx,
20013        )
20014    });
20015
20016    editor.update_in(cx, |editor, window, cx| {
20017        let snapshot = editor.buffer().read(cx).snapshot(cx);
20018        editor.tasks.insert(
20019            (buffer.read(cx).remote_id(), 3),
20020            RunnableTasks {
20021                templates: vec![],
20022                offset: snapshot.anchor_before(43),
20023                column: 0,
20024                extra_variables: HashMap::default(),
20025                context_range: BufferOffset(43)..BufferOffset(85),
20026            },
20027        );
20028        editor.tasks.insert(
20029            (buffer.read(cx).remote_id(), 8),
20030            RunnableTasks {
20031                templates: vec![],
20032                offset: snapshot.anchor_before(86),
20033                column: 0,
20034                extra_variables: HashMap::default(),
20035                context_range: BufferOffset(86)..BufferOffset(191),
20036            },
20037        );
20038
20039        // Test finding task when cursor is inside function body
20040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20041            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20042        });
20043        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20044        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20045
20046        // Test finding task when cursor is on function name
20047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20048            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20049        });
20050        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20051        assert_eq!(row, 8, "Should find task when cursor is on function name");
20052    });
20053}
20054
20055#[gpui::test]
20056async fn test_folding_buffers(cx: &mut TestAppContext) {
20057    init_test(cx, |_| {});
20058
20059    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20060    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20061    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20062
20063    let fs = FakeFs::new(cx.executor());
20064    fs.insert_tree(
20065        path!("/a"),
20066        json!({
20067            "first.rs": sample_text_1,
20068            "second.rs": sample_text_2,
20069            "third.rs": sample_text_3,
20070        }),
20071    )
20072    .await;
20073    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20074    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20075    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20076    let worktree = project.update(cx, |project, cx| {
20077        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20078        assert_eq!(worktrees.len(), 1);
20079        worktrees.pop().unwrap()
20080    });
20081    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20082
20083    let buffer_1 = project
20084        .update(cx, |project, cx| {
20085            project.open_buffer((worktree_id, "first.rs"), cx)
20086        })
20087        .await
20088        .unwrap();
20089    let buffer_2 = project
20090        .update(cx, |project, cx| {
20091            project.open_buffer((worktree_id, "second.rs"), cx)
20092        })
20093        .await
20094        .unwrap();
20095    let buffer_3 = project
20096        .update(cx, |project, cx| {
20097            project.open_buffer((worktree_id, "third.rs"), cx)
20098        })
20099        .await
20100        .unwrap();
20101
20102    let multi_buffer = cx.new(|cx| {
20103        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20104        multi_buffer.push_excerpts(
20105            buffer_1.clone(),
20106            [
20107                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20108                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20109                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20110            ],
20111            cx,
20112        );
20113        multi_buffer.push_excerpts(
20114            buffer_2.clone(),
20115            [
20116                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20117                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20118                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20119            ],
20120            cx,
20121        );
20122        multi_buffer.push_excerpts(
20123            buffer_3.clone(),
20124            [
20125                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20126                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20127                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20128            ],
20129            cx,
20130        );
20131        multi_buffer
20132    });
20133    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20134        Editor::new(
20135            EditorMode::full(),
20136            multi_buffer.clone(),
20137            Some(project.clone()),
20138            window,
20139            cx,
20140        )
20141    });
20142
20143    assert_eq!(
20144        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20145        "\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",
20146    );
20147
20148    multi_buffer_editor.update(cx, |editor, cx| {
20149        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20150    });
20151    assert_eq!(
20152        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20153        "\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",
20154        "After folding the first buffer, its text should not be displayed"
20155    );
20156
20157    multi_buffer_editor.update(cx, |editor, cx| {
20158        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20159    });
20160    assert_eq!(
20161        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20162        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20163        "After folding the second buffer, its text should not be displayed"
20164    );
20165
20166    multi_buffer_editor.update(cx, |editor, cx| {
20167        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20168    });
20169    assert_eq!(
20170        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20171        "\n\n\n\n\n",
20172        "After folding the third buffer, its text should not be displayed"
20173    );
20174
20175    // Emulate selection inside the fold logic, that should work
20176    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20177        editor
20178            .snapshot(window, cx)
20179            .next_line_boundary(Point::new(0, 4));
20180    });
20181
20182    multi_buffer_editor.update(cx, |editor, cx| {
20183        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20184    });
20185    assert_eq!(
20186        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20187        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20188        "After unfolding the second buffer, its text should be displayed"
20189    );
20190
20191    // Typing inside of buffer 1 causes that buffer to be unfolded.
20192    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20193        assert_eq!(
20194            multi_buffer
20195                .read(cx)
20196                .snapshot(cx)
20197                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20198                .collect::<String>(),
20199            "bbbb"
20200        );
20201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20202            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20203        });
20204        editor.handle_input("B", window, cx);
20205    });
20206
20207    assert_eq!(
20208        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20209        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20210        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20211    );
20212
20213    multi_buffer_editor.update(cx, |editor, cx| {
20214        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20215    });
20216    assert_eq!(
20217        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20218        "\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",
20219        "After unfolding the all buffers, all original text should be displayed"
20220    );
20221}
20222
20223#[gpui::test]
20224async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20225    init_test(cx, |_| {});
20226
20227    let sample_text_1 = "1111\n2222\n3333".to_string();
20228    let sample_text_2 = "4444\n5555\n6666".to_string();
20229    let sample_text_3 = "7777\n8888\n9999".to_string();
20230
20231    let fs = FakeFs::new(cx.executor());
20232    fs.insert_tree(
20233        path!("/a"),
20234        json!({
20235            "first.rs": sample_text_1,
20236            "second.rs": sample_text_2,
20237            "third.rs": sample_text_3,
20238        }),
20239    )
20240    .await;
20241    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20242    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20243    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20244    let worktree = project.update(cx, |project, cx| {
20245        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20246        assert_eq!(worktrees.len(), 1);
20247        worktrees.pop().unwrap()
20248    });
20249    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20250
20251    let buffer_1 = project
20252        .update(cx, |project, cx| {
20253            project.open_buffer((worktree_id, "first.rs"), cx)
20254        })
20255        .await
20256        .unwrap();
20257    let buffer_2 = project
20258        .update(cx, |project, cx| {
20259            project.open_buffer((worktree_id, "second.rs"), cx)
20260        })
20261        .await
20262        .unwrap();
20263    let buffer_3 = project
20264        .update(cx, |project, cx| {
20265            project.open_buffer((worktree_id, "third.rs"), cx)
20266        })
20267        .await
20268        .unwrap();
20269
20270    let multi_buffer = cx.new(|cx| {
20271        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20272        multi_buffer.push_excerpts(
20273            buffer_1.clone(),
20274            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20275            cx,
20276        );
20277        multi_buffer.push_excerpts(
20278            buffer_2.clone(),
20279            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20280            cx,
20281        );
20282        multi_buffer.push_excerpts(
20283            buffer_3.clone(),
20284            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20285            cx,
20286        );
20287        multi_buffer
20288    });
20289
20290    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20291        Editor::new(
20292            EditorMode::full(),
20293            multi_buffer,
20294            Some(project.clone()),
20295            window,
20296            cx,
20297        )
20298    });
20299
20300    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20301    assert_eq!(
20302        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20303        full_text,
20304    );
20305
20306    multi_buffer_editor.update(cx, |editor, cx| {
20307        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20308    });
20309    assert_eq!(
20310        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20311        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20312        "After folding the first buffer, its text should not be displayed"
20313    );
20314
20315    multi_buffer_editor.update(cx, |editor, cx| {
20316        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20317    });
20318
20319    assert_eq!(
20320        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20321        "\n\n\n\n\n\n7777\n8888\n9999",
20322        "After folding the second buffer, its text should not be displayed"
20323    );
20324
20325    multi_buffer_editor.update(cx, |editor, cx| {
20326        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20327    });
20328    assert_eq!(
20329        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20330        "\n\n\n\n\n",
20331        "After folding the third buffer, its text should not be displayed"
20332    );
20333
20334    multi_buffer_editor.update(cx, |editor, cx| {
20335        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20336    });
20337    assert_eq!(
20338        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20339        "\n\n\n\n4444\n5555\n6666\n\n",
20340        "After unfolding the second buffer, its text should be displayed"
20341    );
20342
20343    multi_buffer_editor.update(cx, |editor, cx| {
20344        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20345    });
20346    assert_eq!(
20347        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20348        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20349        "After unfolding the first buffer, its text should be displayed"
20350    );
20351
20352    multi_buffer_editor.update(cx, |editor, cx| {
20353        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20354    });
20355    assert_eq!(
20356        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20357        full_text,
20358        "After unfolding all buffers, all original text should be displayed"
20359    );
20360}
20361
20362#[gpui::test]
20363async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20364    init_test(cx, |_| {});
20365
20366    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20367
20368    let fs = FakeFs::new(cx.executor());
20369    fs.insert_tree(
20370        path!("/a"),
20371        json!({
20372            "main.rs": sample_text,
20373        }),
20374    )
20375    .await;
20376    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20377    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20378    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20379    let worktree = project.update(cx, |project, cx| {
20380        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20381        assert_eq!(worktrees.len(), 1);
20382        worktrees.pop().unwrap()
20383    });
20384    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20385
20386    let buffer_1 = project
20387        .update(cx, |project, cx| {
20388            project.open_buffer((worktree_id, "main.rs"), cx)
20389        })
20390        .await
20391        .unwrap();
20392
20393    let multi_buffer = cx.new(|cx| {
20394        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20395        multi_buffer.push_excerpts(
20396            buffer_1.clone(),
20397            [ExcerptRange::new(
20398                Point::new(0, 0)
20399                    ..Point::new(
20400                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20401                        0,
20402                    ),
20403            )],
20404            cx,
20405        );
20406        multi_buffer
20407    });
20408    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20409        Editor::new(
20410            EditorMode::full(),
20411            multi_buffer,
20412            Some(project.clone()),
20413            window,
20414            cx,
20415        )
20416    });
20417
20418    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20419    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20420        enum TestHighlight {}
20421        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20422        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20423        editor.highlight_text::<TestHighlight>(
20424            vec![highlight_range.clone()],
20425            HighlightStyle::color(Hsla::green()),
20426            cx,
20427        );
20428        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20429            s.select_ranges(Some(highlight_range))
20430        });
20431    });
20432
20433    let full_text = format!("\n\n{sample_text}");
20434    assert_eq!(
20435        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20436        full_text,
20437    );
20438}
20439
20440#[gpui::test]
20441async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20442    init_test(cx, |_| {});
20443    cx.update(|cx| {
20444        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20445            "keymaps/default-linux.json",
20446            cx,
20447        )
20448        .unwrap();
20449        cx.bind_keys(default_key_bindings);
20450    });
20451
20452    let (editor, cx) = cx.add_window_view(|window, cx| {
20453        let multi_buffer = MultiBuffer::build_multi(
20454            [
20455                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20456                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20457                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20458                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20459            ],
20460            cx,
20461        );
20462        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20463
20464        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20465        // fold all but the second buffer, so that we test navigating between two
20466        // adjacent folded buffers, as well as folded buffers at the start and
20467        // end the multibuffer
20468        editor.fold_buffer(buffer_ids[0], cx);
20469        editor.fold_buffer(buffer_ids[2], cx);
20470        editor.fold_buffer(buffer_ids[3], cx);
20471
20472        editor
20473    });
20474    cx.simulate_resize(size(px(1000.), px(1000.)));
20475
20476    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20477    cx.assert_excerpts_with_selections(indoc! {"
20478        [EXCERPT]
20479        ˇ[FOLDED]
20480        [EXCERPT]
20481        a1
20482        b1
20483        [EXCERPT]
20484        [FOLDED]
20485        [EXCERPT]
20486        [FOLDED]
20487        "
20488    });
20489    cx.simulate_keystroke("down");
20490    cx.assert_excerpts_with_selections(indoc! {"
20491        [EXCERPT]
20492        [FOLDED]
20493        [EXCERPT]
20494        ˇa1
20495        b1
20496        [EXCERPT]
20497        [FOLDED]
20498        [EXCERPT]
20499        [FOLDED]
20500        "
20501    });
20502    cx.simulate_keystroke("down");
20503    cx.assert_excerpts_with_selections(indoc! {"
20504        [EXCERPT]
20505        [FOLDED]
20506        [EXCERPT]
20507        a1
20508        ˇb1
20509        [EXCERPT]
20510        [FOLDED]
20511        [EXCERPT]
20512        [FOLDED]
20513        "
20514    });
20515    cx.simulate_keystroke("down");
20516    cx.assert_excerpts_with_selections(indoc! {"
20517        [EXCERPT]
20518        [FOLDED]
20519        [EXCERPT]
20520        a1
20521        b1
20522        ˇ[EXCERPT]
20523        [FOLDED]
20524        [EXCERPT]
20525        [FOLDED]
20526        "
20527    });
20528    cx.simulate_keystroke("down");
20529    cx.assert_excerpts_with_selections(indoc! {"
20530        [EXCERPT]
20531        [FOLDED]
20532        [EXCERPT]
20533        a1
20534        b1
20535        [EXCERPT]
20536        ˇ[FOLDED]
20537        [EXCERPT]
20538        [FOLDED]
20539        "
20540    });
20541    for _ in 0..5 {
20542        cx.simulate_keystroke("down");
20543        cx.assert_excerpts_with_selections(indoc! {"
20544            [EXCERPT]
20545            [FOLDED]
20546            [EXCERPT]
20547            a1
20548            b1
20549            [EXCERPT]
20550            [FOLDED]
20551            [EXCERPT]
20552            ˇ[FOLDED]
20553            "
20554        });
20555    }
20556
20557    cx.simulate_keystroke("up");
20558    cx.assert_excerpts_with_selections(indoc! {"
20559        [EXCERPT]
20560        [FOLDED]
20561        [EXCERPT]
20562        a1
20563        b1
20564        [EXCERPT]
20565        ˇ[FOLDED]
20566        [EXCERPT]
20567        [FOLDED]
20568        "
20569    });
20570    cx.simulate_keystroke("up");
20571    cx.assert_excerpts_with_selections(indoc! {"
20572        [EXCERPT]
20573        [FOLDED]
20574        [EXCERPT]
20575        a1
20576        b1
20577        ˇ[EXCERPT]
20578        [FOLDED]
20579        [EXCERPT]
20580        [FOLDED]
20581        "
20582    });
20583    cx.simulate_keystroke("up");
20584    cx.assert_excerpts_with_selections(indoc! {"
20585        [EXCERPT]
20586        [FOLDED]
20587        [EXCERPT]
20588        a1
20589        ˇb1
20590        [EXCERPT]
20591        [FOLDED]
20592        [EXCERPT]
20593        [FOLDED]
20594        "
20595    });
20596    cx.simulate_keystroke("up");
20597    cx.assert_excerpts_with_selections(indoc! {"
20598        [EXCERPT]
20599        [FOLDED]
20600        [EXCERPT]
20601        ˇa1
20602        b1
20603        [EXCERPT]
20604        [FOLDED]
20605        [EXCERPT]
20606        [FOLDED]
20607        "
20608    });
20609    for _ in 0..5 {
20610        cx.simulate_keystroke("up");
20611        cx.assert_excerpts_with_selections(indoc! {"
20612            [EXCERPT]
20613            ˇ[FOLDED]
20614            [EXCERPT]
20615            a1
20616            b1
20617            [EXCERPT]
20618            [FOLDED]
20619            [EXCERPT]
20620            [FOLDED]
20621            "
20622        });
20623    }
20624}
20625
20626#[gpui::test]
20627async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20628    init_test(cx, |_| {});
20629
20630    // Simple insertion
20631    assert_highlighted_edits(
20632        "Hello, world!",
20633        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20634        true,
20635        cx,
20636        |highlighted_edits, cx| {
20637            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20638            assert_eq!(highlighted_edits.highlights.len(), 1);
20639            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20640            assert_eq!(
20641                highlighted_edits.highlights[0].1.background_color,
20642                Some(cx.theme().status().created_background)
20643            );
20644        },
20645    )
20646    .await;
20647
20648    // Replacement
20649    assert_highlighted_edits(
20650        "This is a test.",
20651        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20652        false,
20653        cx,
20654        |highlighted_edits, cx| {
20655            assert_eq!(highlighted_edits.text, "That is a test.");
20656            assert_eq!(highlighted_edits.highlights.len(), 1);
20657            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20658            assert_eq!(
20659                highlighted_edits.highlights[0].1.background_color,
20660                Some(cx.theme().status().created_background)
20661            );
20662        },
20663    )
20664    .await;
20665
20666    // Multiple edits
20667    assert_highlighted_edits(
20668        "Hello, world!",
20669        vec![
20670            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20671            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20672        ],
20673        false,
20674        cx,
20675        |highlighted_edits, cx| {
20676            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20677            assert_eq!(highlighted_edits.highlights.len(), 2);
20678            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20679            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20680            assert_eq!(
20681                highlighted_edits.highlights[0].1.background_color,
20682                Some(cx.theme().status().created_background)
20683            );
20684            assert_eq!(
20685                highlighted_edits.highlights[1].1.background_color,
20686                Some(cx.theme().status().created_background)
20687            );
20688        },
20689    )
20690    .await;
20691
20692    // Multiple lines with edits
20693    assert_highlighted_edits(
20694        "First line\nSecond line\nThird line\nFourth line",
20695        vec![
20696            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20697            (
20698                Point::new(2, 0)..Point::new(2, 10),
20699                "New third line".to_string(),
20700            ),
20701            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20702        ],
20703        false,
20704        cx,
20705        |highlighted_edits, cx| {
20706            assert_eq!(
20707                highlighted_edits.text,
20708                "Second modified\nNew third line\nFourth updated line"
20709            );
20710            assert_eq!(highlighted_edits.highlights.len(), 3);
20711            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20712            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20713            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20714            for highlight in &highlighted_edits.highlights {
20715                assert_eq!(
20716                    highlight.1.background_color,
20717                    Some(cx.theme().status().created_background)
20718                );
20719            }
20720        },
20721    )
20722    .await;
20723}
20724
20725#[gpui::test]
20726async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20727    init_test(cx, |_| {});
20728
20729    // Deletion
20730    assert_highlighted_edits(
20731        "Hello, world!",
20732        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20733        true,
20734        cx,
20735        |highlighted_edits, cx| {
20736            assert_eq!(highlighted_edits.text, "Hello, world!");
20737            assert_eq!(highlighted_edits.highlights.len(), 1);
20738            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20739            assert_eq!(
20740                highlighted_edits.highlights[0].1.background_color,
20741                Some(cx.theme().status().deleted_background)
20742            );
20743        },
20744    )
20745    .await;
20746
20747    // Insertion
20748    assert_highlighted_edits(
20749        "Hello, world!",
20750        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20751        true,
20752        cx,
20753        |highlighted_edits, cx| {
20754            assert_eq!(highlighted_edits.highlights.len(), 1);
20755            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20756            assert_eq!(
20757                highlighted_edits.highlights[0].1.background_color,
20758                Some(cx.theme().status().created_background)
20759            );
20760        },
20761    )
20762    .await;
20763}
20764
20765async fn assert_highlighted_edits(
20766    text: &str,
20767    edits: Vec<(Range<Point>, String)>,
20768    include_deletions: bool,
20769    cx: &mut TestAppContext,
20770    assertion_fn: impl Fn(HighlightedText, &App),
20771) {
20772    let window = cx.add_window(|window, cx| {
20773        let buffer = MultiBuffer::build_simple(text, cx);
20774        Editor::new(EditorMode::full(), buffer, None, window, cx)
20775    });
20776    let cx = &mut VisualTestContext::from_window(*window, cx);
20777
20778    let (buffer, snapshot) = window
20779        .update(cx, |editor, _window, cx| {
20780            (
20781                editor.buffer().clone(),
20782                editor.buffer().read(cx).snapshot(cx),
20783            )
20784        })
20785        .unwrap();
20786
20787    let edits = edits
20788        .into_iter()
20789        .map(|(range, edit)| {
20790            (
20791                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20792                edit,
20793            )
20794        })
20795        .collect::<Vec<_>>();
20796
20797    let text_anchor_edits = edits
20798        .clone()
20799        .into_iter()
20800        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20801        .collect::<Vec<_>>();
20802
20803    let edit_preview = window
20804        .update(cx, |_, _window, cx| {
20805            buffer
20806                .read(cx)
20807                .as_singleton()
20808                .unwrap()
20809                .read(cx)
20810                .preview_edits(text_anchor_edits.into(), cx)
20811        })
20812        .unwrap()
20813        .await;
20814
20815    cx.update(|_window, cx| {
20816        let highlighted_edits = edit_prediction_edit_text(
20817            &snapshot.as_singleton().unwrap().2,
20818            &edits,
20819            &edit_preview,
20820            include_deletions,
20821            cx,
20822        );
20823        assertion_fn(highlighted_edits, cx)
20824    });
20825}
20826
20827#[track_caller]
20828fn assert_breakpoint(
20829    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20830    path: &Arc<Path>,
20831    expected: Vec<(u32, Breakpoint)>,
20832) {
20833    if expected.len() == 0usize {
20834        assert!(!breakpoints.contains_key(path), "{}", path.display());
20835    } else {
20836        let mut breakpoint = breakpoints
20837            .get(path)
20838            .unwrap()
20839            .into_iter()
20840            .map(|breakpoint| {
20841                (
20842                    breakpoint.row,
20843                    Breakpoint {
20844                        message: breakpoint.message.clone(),
20845                        state: breakpoint.state,
20846                        condition: breakpoint.condition.clone(),
20847                        hit_condition: breakpoint.hit_condition.clone(),
20848                    },
20849                )
20850            })
20851            .collect::<Vec<_>>();
20852
20853        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20854
20855        assert_eq!(expected, breakpoint);
20856    }
20857}
20858
20859fn add_log_breakpoint_at_cursor(
20860    editor: &mut Editor,
20861    log_message: &str,
20862    window: &mut Window,
20863    cx: &mut Context<Editor>,
20864) {
20865    let (anchor, bp) = editor
20866        .breakpoints_at_cursors(window, cx)
20867        .first()
20868        .and_then(|(anchor, bp)| {
20869            if let Some(bp) = bp {
20870                Some((*anchor, bp.clone()))
20871            } else {
20872                None
20873            }
20874        })
20875        .unwrap_or_else(|| {
20876            let cursor_position: Point = editor.selections.newest(cx).head();
20877
20878            let breakpoint_position = editor
20879                .snapshot(window, cx)
20880                .display_snapshot
20881                .buffer_snapshot
20882                .anchor_before(Point::new(cursor_position.row, 0));
20883
20884            (breakpoint_position, Breakpoint::new_log(&log_message))
20885        });
20886
20887    editor.edit_breakpoint_at_anchor(
20888        anchor,
20889        bp,
20890        BreakpointEditAction::EditLogMessage(log_message.into()),
20891        cx,
20892    );
20893}
20894
20895#[gpui::test]
20896async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20897    init_test(cx, |_| {});
20898
20899    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20900    let fs = FakeFs::new(cx.executor());
20901    fs.insert_tree(
20902        path!("/a"),
20903        json!({
20904            "main.rs": sample_text,
20905        }),
20906    )
20907    .await;
20908    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20909    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20910    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20911
20912    let fs = FakeFs::new(cx.executor());
20913    fs.insert_tree(
20914        path!("/a"),
20915        json!({
20916            "main.rs": sample_text,
20917        }),
20918    )
20919    .await;
20920    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20921    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20922    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20923    let worktree_id = workspace
20924        .update(cx, |workspace, _window, cx| {
20925            workspace.project().update(cx, |project, cx| {
20926                project.worktrees(cx).next().unwrap().read(cx).id()
20927            })
20928        })
20929        .unwrap();
20930
20931    let buffer = project
20932        .update(cx, |project, cx| {
20933            project.open_buffer((worktree_id, "main.rs"), cx)
20934        })
20935        .await
20936        .unwrap();
20937
20938    let (editor, cx) = cx.add_window_view(|window, cx| {
20939        Editor::new(
20940            EditorMode::full(),
20941            MultiBuffer::build_from_buffer(buffer, cx),
20942            Some(project.clone()),
20943            window,
20944            cx,
20945        )
20946    });
20947
20948    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20949    let abs_path = project.read_with(cx, |project, cx| {
20950        project
20951            .absolute_path(&project_path, cx)
20952            .map(|path_buf| Arc::from(path_buf.to_owned()))
20953            .unwrap()
20954    });
20955
20956    // assert we can add breakpoint on the first line
20957    editor.update_in(cx, |editor, window, cx| {
20958        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20959        editor.move_to_end(&MoveToEnd, window, cx);
20960        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20961    });
20962
20963    let breakpoints = editor.update(cx, |editor, cx| {
20964        editor
20965            .breakpoint_store()
20966            .as_ref()
20967            .unwrap()
20968            .read(cx)
20969            .all_source_breakpoints(cx)
20970            .clone()
20971    });
20972
20973    assert_eq!(1, breakpoints.len());
20974    assert_breakpoint(
20975        &breakpoints,
20976        &abs_path,
20977        vec![
20978            (0, Breakpoint::new_standard()),
20979            (3, Breakpoint::new_standard()),
20980        ],
20981    );
20982
20983    editor.update_in(cx, |editor, window, cx| {
20984        editor.move_to_beginning(&MoveToBeginning, window, cx);
20985        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20986    });
20987
20988    let breakpoints = editor.update(cx, |editor, cx| {
20989        editor
20990            .breakpoint_store()
20991            .as_ref()
20992            .unwrap()
20993            .read(cx)
20994            .all_source_breakpoints(cx)
20995            .clone()
20996    });
20997
20998    assert_eq!(1, breakpoints.len());
20999    assert_breakpoint(
21000        &breakpoints,
21001        &abs_path,
21002        vec![(3, Breakpoint::new_standard())],
21003    );
21004
21005    editor.update_in(cx, |editor, window, cx| {
21006        editor.move_to_end(&MoveToEnd, window, cx);
21007        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21008    });
21009
21010    let breakpoints = editor.update(cx, |editor, cx| {
21011        editor
21012            .breakpoint_store()
21013            .as_ref()
21014            .unwrap()
21015            .read(cx)
21016            .all_source_breakpoints(cx)
21017            .clone()
21018    });
21019
21020    assert_eq!(0, breakpoints.len());
21021    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21022}
21023
21024#[gpui::test]
21025async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21026    init_test(cx, |_| {});
21027
21028    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21029
21030    let fs = FakeFs::new(cx.executor());
21031    fs.insert_tree(
21032        path!("/a"),
21033        json!({
21034            "main.rs": sample_text,
21035        }),
21036    )
21037    .await;
21038    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21039    let (workspace, cx) =
21040        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21041
21042    let worktree_id = workspace.update(cx, |workspace, cx| {
21043        workspace.project().update(cx, |project, cx| {
21044            project.worktrees(cx).next().unwrap().read(cx).id()
21045        })
21046    });
21047
21048    let buffer = project
21049        .update(cx, |project, cx| {
21050            project.open_buffer((worktree_id, "main.rs"), cx)
21051        })
21052        .await
21053        .unwrap();
21054
21055    let (editor, cx) = cx.add_window_view(|window, cx| {
21056        Editor::new(
21057            EditorMode::full(),
21058            MultiBuffer::build_from_buffer(buffer, cx),
21059            Some(project.clone()),
21060            window,
21061            cx,
21062        )
21063    });
21064
21065    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21066    let abs_path = project.read_with(cx, |project, cx| {
21067        project
21068            .absolute_path(&project_path, cx)
21069            .map(|path_buf| Arc::from(path_buf.to_owned()))
21070            .unwrap()
21071    });
21072
21073    editor.update_in(cx, |editor, window, cx| {
21074        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21075    });
21076
21077    let breakpoints = editor.update(cx, |editor, cx| {
21078        editor
21079            .breakpoint_store()
21080            .as_ref()
21081            .unwrap()
21082            .read(cx)
21083            .all_source_breakpoints(cx)
21084            .clone()
21085    });
21086
21087    assert_breakpoint(
21088        &breakpoints,
21089        &abs_path,
21090        vec![(0, Breakpoint::new_log("hello world"))],
21091    );
21092
21093    // Removing a log message from a log breakpoint should remove it
21094    editor.update_in(cx, |editor, window, cx| {
21095        add_log_breakpoint_at_cursor(editor, "", window, cx);
21096    });
21097
21098    let breakpoints = editor.update(cx, |editor, cx| {
21099        editor
21100            .breakpoint_store()
21101            .as_ref()
21102            .unwrap()
21103            .read(cx)
21104            .all_source_breakpoints(cx)
21105            .clone()
21106    });
21107
21108    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21109
21110    editor.update_in(cx, |editor, window, cx| {
21111        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21112        editor.move_to_end(&MoveToEnd, window, cx);
21113        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21114        // Not adding a log message to a standard breakpoint shouldn't remove it
21115        add_log_breakpoint_at_cursor(editor, "", window, cx);
21116    });
21117
21118    let breakpoints = editor.update(cx, |editor, cx| {
21119        editor
21120            .breakpoint_store()
21121            .as_ref()
21122            .unwrap()
21123            .read(cx)
21124            .all_source_breakpoints(cx)
21125            .clone()
21126    });
21127
21128    assert_breakpoint(
21129        &breakpoints,
21130        &abs_path,
21131        vec![
21132            (0, Breakpoint::new_standard()),
21133            (3, Breakpoint::new_standard()),
21134        ],
21135    );
21136
21137    editor.update_in(cx, |editor, window, cx| {
21138        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21139    });
21140
21141    let breakpoints = editor.update(cx, |editor, cx| {
21142        editor
21143            .breakpoint_store()
21144            .as_ref()
21145            .unwrap()
21146            .read(cx)
21147            .all_source_breakpoints(cx)
21148            .clone()
21149    });
21150
21151    assert_breakpoint(
21152        &breakpoints,
21153        &abs_path,
21154        vec![
21155            (0, Breakpoint::new_standard()),
21156            (3, Breakpoint::new_log("hello world")),
21157        ],
21158    );
21159
21160    editor.update_in(cx, |editor, window, cx| {
21161        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21162    });
21163
21164    let breakpoints = editor.update(cx, |editor, cx| {
21165        editor
21166            .breakpoint_store()
21167            .as_ref()
21168            .unwrap()
21169            .read(cx)
21170            .all_source_breakpoints(cx)
21171            .clone()
21172    });
21173
21174    assert_breakpoint(
21175        &breakpoints,
21176        &abs_path,
21177        vec![
21178            (0, Breakpoint::new_standard()),
21179            (3, Breakpoint::new_log("hello Earth!!")),
21180        ],
21181    );
21182}
21183
21184/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21185/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21186/// or when breakpoints were placed out of order. This tests for a regression too
21187#[gpui::test]
21188async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21189    init_test(cx, |_| {});
21190
21191    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21192    let fs = FakeFs::new(cx.executor());
21193    fs.insert_tree(
21194        path!("/a"),
21195        json!({
21196            "main.rs": sample_text,
21197        }),
21198    )
21199    .await;
21200    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21201    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21202    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21203
21204    let fs = FakeFs::new(cx.executor());
21205    fs.insert_tree(
21206        path!("/a"),
21207        json!({
21208            "main.rs": sample_text,
21209        }),
21210    )
21211    .await;
21212    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21213    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21214    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21215    let worktree_id = workspace
21216        .update(cx, |workspace, _window, cx| {
21217            workspace.project().update(cx, |project, cx| {
21218                project.worktrees(cx).next().unwrap().read(cx).id()
21219            })
21220        })
21221        .unwrap();
21222
21223    let buffer = project
21224        .update(cx, |project, cx| {
21225            project.open_buffer((worktree_id, "main.rs"), cx)
21226        })
21227        .await
21228        .unwrap();
21229
21230    let (editor, cx) = cx.add_window_view(|window, cx| {
21231        Editor::new(
21232            EditorMode::full(),
21233            MultiBuffer::build_from_buffer(buffer, cx),
21234            Some(project.clone()),
21235            window,
21236            cx,
21237        )
21238    });
21239
21240    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21241    let abs_path = project.read_with(cx, |project, cx| {
21242        project
21243            .absolute_path(&project_path, cx)
21244            .map(|path_buf| Arc::from(path_buf.to_owned()))
21245            .unwrap()
21246    });
21247
21248    // assert we can add breakpoint on the first line
21249    editor.update_in(cx, |editor, window, cx| {
21250        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21251        editor.move_to_end(&MoveToEnd, window, cx);
21252        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21253        editor.move_up(&MoveUp, window, cx);
21254        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21255    });
21256
21257    let breakpoints = editor.update(cx, |editor, cx| {
21258        editor
21259            .breakpoint_store()
21260            .as_ref()
21261            .unwrap()
21262            .read(cx)
21263            .all_source_breakpoints(cx)
21264            .clone()
21265    });
21266
21267    assert_eq!(1, breakpoints.len());
21268    assert_breakpoint(
21269        &breakpoints,
21270        &abs_path,
21271        vec![
21272            (0, Breakpoint::new_standard()),
21273            (2, Breakpoint::new_standard()),
21274            (3, Breakpoint::new_standard()),
21275        ],
21276    );
21277
21278    editor.update_in(cx, |editor, window, cx| {
21279        editor.move_to_beginning(&MoveToBeginning, window, cx);
21280        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21281        editor.move_to_end(&MoveToEnd, window, cx);
21282        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21283        // Disabling a breakpoint that doesn't exist should do nothing
21284        editor.move_up(&MoveUp, window, cx);
21285        editor.move_up(&MoveUp, window, cx);
21286        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21287    });
21288
21289    let breakpoints = editor.update(cx, |editor, cx| {
21290        editor
21291            .breakpoint_store()
21292            .as_ref()
21293            .unwrap()
21294            .read(cx)
21295            .all_source_breakpoints(cx)
21296            .clone()
21297    });
21298
21299    let disable_breakpoint = {
21300        let mut bp = Breakpoint::new_standard();
21301        bp.state = BreakpointState::Disabled;
21302        bp
21303    };
21304
21305    assert_eq!(1, breakpoints.len());
21306    assert_breakpoint(
21307        &breakpoints,
21308        &abs_path,
21309        vec![
21310            (0, disable_breakpoint.clone()),
21311            (2, Breakpoint::new_standard()),
21312            (3, disable_breakpoint.clone()),
21313        ],
21314    );
21315
21316    editor.update_in(cx, |editor, window, cx| {
21317        editor.move_to_beginning(&MoveToBeginning, window, cx);
21318        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21319        editor.move_to_end(&MoveToEnd, window, cx);
21320        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21321        editor.move_up(&MoveUp, window, cx);
21322        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21323    });
21324
21325    let breakpoints = editor.update(cx, |editor, cx| {
21326        editor
21327            .breakpoint_store()
21328            .as_ref()
21329            .unwrap()
21330            .read(cx)
21331            .all_source_breakpoints(cx)
21332            .clone()
21333    });
21334
21335    assert_eq!(1, breakpoints.len());
21336    assert_breakpoint(
21337        &breakpoints,
21338        &abs_path,
21339        vec![
21340            (0, Breakpoint::new_standard()),
21341            (2, disable_breakpoint),
21342            (3, Breakpoint::new_standard()),
21343        ],
21344    );
21345}
21346
21347#[gpui::test]
21348async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21349    init_test(cx, |_| {});
21350    let capabilities = lsp::ServerCapabilities {
21351        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21352            prepare_provider: Some(true),
21353            work_done_progress_options: Default::default(),
21354        })),
21355        ..Default::default()
21356    };
21357    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21358
21359    cx.set_state(indoc! {"
21360        struct Fˇoo {}
21361    "});
21362
21363    cx.update_editor(|editor, _, cx| {
21364        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21365        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21366        editor.highlight_background::<DocumentHighlightRead>(
21367            &[highlight_range],
21368            |theme| theme.colors().editor_document_highlight_read_background,
21369            cx,
21370        );
21371    });
21372
21373    let mut prepare_rename_handler = cx
21374        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21375            move |_, _, _| async move {
21376                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21377                    start: lsp::Position {
21378                        line: 0,
21379                        character: 7,
21380                    },
21381                    end: lsp::Position {
21382                        line: 0,
21383                        character: 10,
21384                    },
21385                })))
21386            },
21387        );
21388    let prepare_rename_task = cx
21389        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21390        .expect("Prepare rename was not started");
21391    prepare_rename_handler.next().await.unwrap();
21392    prepare_rename_task.await.expect("Prepare rename failed");
21393
21394    let mut rename_handler =
21395        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21396            let edit = lsp::TextEdit {
21397                range: lsp::Range {
21398                    start: lsp::Position {
21399                        line: 0,
21400                        character: 7,
21401                    },
21402                    end: lsp::Position {
21403                        line: 0,
21404                        character: 10,
21405                    },
21406                },
21407                new_text: "FooRenamed".to_string(),
21408            };
21409            Ok(Some(lsp::WorkspaceEdit::new(
21410                // Specify the same edit twice
21411                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21412            )))
21413        });
21414    let rename_task = cx
21415        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21416        .expect("Confirm rename was not started");
21417    rename_handler.next().await.unwrap();
21418    rename_task.await.expect("Confirm rename failed");
21419    cx.run_until_parked();
21420
21421    // Despite two edits, only one is actually applied as those are identical
21422    cx.assert_editor_state(indoc! {"
21423        struct FooRenamedˇ {}
21424    "});
21425}
21426
21427#[gpui::test]
21428async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21429    init_test(cx, |_| {});
21430    // These capabilities indicate that the server does not support prepare rename.
21431    let capabilities = lsp::ServerCapabilities {
21432        rename_provider: Some(lsp::OneOf::Left(true)),
21433        ..Default::default()
21434    };
21435    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21436
21437    cx.set_state(indoc! {"
21438        struct Fˇoo {}
21439    "});
21440
21441    cx.update_editor(|editor, _window, cx| {
21442        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21443        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21444        editor.highlight_background::<DocumentHighlightRead>(
21445            &[highlight_range],
21446            |theme| theme.colors().editor_document_highlight_read_background,
21447            cx,
21448        );
21449    });
21450
21451    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21452        .expect("Prepare rename was not started")
21453        .await
21454        .expect("Prepare rename failed");
21455
21456    let mut rename_handler =
21457        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21458            let edit = lsp::TextEdit {
21459                range: lsp::Range {
21460                    start: lsp::Position {
21461                        line: 0,
21462                        character: 7,
21463                    },
21464                    end: lsp::Position {
21465                        line: 0,
21466                        character: 10,
21467                    },
21468                },
21469                new_text: "FooRenamed".to_string(),
21470            };
21471            Ok(Some(lsp::WorkspaceEdit::new(
21472                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21473            )))
21474        });
21475    let rename_task = cx
21476        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21477        .expect("Confirm rename was not started");
21478    rename_handler.next().await.unwrap();
21479    rename_task.await.expect("Confirm rename failed");
21480    cx.run_until_parked();
21481
21482    // Correct range is renamed, as `surrounding_word` is used to find it.
21483    cx.assert_editor_state(indoc! {"
21484        struct FooRenamedˇ {}
21485    "});
21486}
21487
21488#[gpui::test]
21489async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21490    init_test(cx, |_| {});
21491    let mut cx = EditorTestContext::new(cx).await;
21492
21493    let language = Arc::new(
21494        Language::new(
21495            LanguageConfig::default(),
21496            Some(tree_sitter_html::LANGUAGE.into()),
21497        )
21498        .with_brackets_query(
21499            r#"
21500            ("<" @open "/>" @close)
21501            ("</" @open ">" @close)
21502            ("<" @open ">" @close)
21503            ("\"" @open "\"" @close)
21504            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21505        "#,
21506        )
21507        .unwrap(),
21508    );
21509    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21510
21511    cx.set_state(indoc! {"
21512        <span>ˇ</span>
21513    "});
21514    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21515    cx.assert_editor_state(indoc! {"
21516        <span>
21517        ˇ
21518        </span>
21519    "});
21520
21521    cx.set_state(indoc! {"
21522        <span><span></span>ˇ</span>
21523    "});
21524    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21525    cx.assert_editor_state(indoc! {"
21526        <span><span></span>
21527        ˇ</span>
21528    "});
21529
21530    cx.set_state(indoc! {"
21531        <span>ˇ
21532        </span>
21533    "});
21534    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21535    cx.assert_editor_state(indoc! {"
21536        <span>
21537        ˇ
21538        </span>
21539    "});
21540}
21541
21542#[gpui::test(iterations = 10)]
21543async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21544    init_test(cx, |_| {});
21545
21546    let fs = FakeFs::new(cx.executor());
21547    fs.insert_tree(
21548        path!("/dir"),
21549        json!({
21550            "a.ts": "a",
21551        }),
21552    )
21553    .await;
21554
21555    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21556    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21557    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21558
21559    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21560    language_registry.add(Arc::new(Language::new(
21561        LanguageConfig {
21562            name: "TypeScript".into(),
21563            matcher: LanguageMatcher {
21564                path_suffixes: vec!["ts".to_string()],
21565                ..Default::default()
21566            },
21567            ..Default::default()
21568        },
21569        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21570    )));
21571    let mut fake_language_servers = language_registry.register_fake_lsp(
21572        "TypeScript",
21573        FakeLspAdapter {
21574            capabilities: lsp::ServerCapabilities {
21575                code_lens_provider: Some(lsp::CodeLensOptions {
21576                    resolve_provider: Some(true),
21577                }),
21578                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21579                    commands: vec!["_the/command".to_string()],
21580                    ..lsp::ExecuteCommandOptions::default()
21581                }),
21582                ..lsp::ServerCapabilities::default()
21583            },
21584            ..FakeLspAdapter::default()
21585        },
21586    );
21587
21588    let editor = workspace
21589        .update(cx, |workspace, window, cx| {
21590            workspace.open_abs_path(
21591                PathBuf::from(path!("/dir/a.ts")),
21592                OpenOptions::default(),
21593                window,
21594                cx,
21595            )
21596        })
21597        .unwrap()
21598        .await
21599        .unwrap()
21600        .downcast::<Editor>()
21601        .unwrap();
21602    cx.executor().run_until_parked();
21603
21604    let fake_server = fake_language_servers.next().await.unwrap();
21605
21606    let buffer = editor.update(cx, |editor, cx| {
21607        editor
21608            .buffer()
21609            .read(cx)
21610            .as_singleton()
21611            .expect("have opened a single file by path")
21612    });
21613
21614    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21615    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21616    drop(buffer_snapshot);
21617    let actions = cx
21618        .update_window(*workspace, |_, window, cx| {
21619            project.code_actions(&buffer, anchor..anchor, window, cx)
21620        })
21621        .unwrap();
21622
21623    fake_server
21624        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21625            Ok(Some(vec![
21626                lsp::CodeLens {
21627                    range: lsp::Range::default(),
21628                    command: Some(lsp::Command {
21629                        title: "Code lens command".to_owned(),
21630                        command: "_the/command".to_owned(),
21631                        arguments: None,
21632                    }),
21633                    data: None,
21634                },
21635                lsp::CodeLens {
21636                    range: lsp::Range::default(),
21637                    command: Some(lsp::Command {
21638                        title: "Command not in capabilities".to_owned(),
21639                        command: "not in capabilities".to_owned(),
21640                        arguments: None,
21641                    }),
21642                    data: None,
21643                },
21644                lsp::CodeLens {
21645                    range: lsp::Range {
21646                        start: lsp::Position {
21647                            line: 1,
21648                            character: 1,
21649                        },
21650                        end: lsp::Position {
21651                            line: 1,
21652                            character: 1,
21653                        },
21654                    },
21655                    command: Some(lsp::Command {
21656                        title: "Command not in range".to_owned(),
21657                        command: "_the/command".to_owned(),
21658                        arguments: None,
21659                    }),
21660                    data: None,
21661                },
21662            ]))
21663        })
21664        .next()
21665        .await;
21666
21667    let actions = actions.await.unwrap();
21668    assert_eq!(
21669        actions.len(),
21670        1,
21671        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21672    );
21673    let action = actions[0].clone();
21674    let apply = project.update(cx, |project, cx| {
21675        project.apply_code_action(buffer.clone(), action, true, cx)
21676    });
21677
21678    // Resolving the code action does not populate its edits. In absence of
21679    // edits, we must execute the given command.
21680    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21681        |mut lens, _| async move {
21682            let lens_command = lens.command.as_mut().expect("should have a command");
21683            assert_eq!(lens_command.title, "Code lens command");
21684            lens_command.arguments = Some(vec![json!("the-argument")]);
21685            Ok(lens)
21686        },
21687    );
21688
21689    // While executing the command, the language server sends the editor
21690    // a `workspaceEdit` request.
21691    fake_server
21692        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21693            let fake = fake_server.clone();
21694            move |params, _| {
21695                assert_eq!(params.command, "_the/command");
21696                let fake = fake.clone();
21697                async move {
21698                    fake.server
21699                        .request::<lsp::request::ApplyWorkspaceEdit>(
21700                            lsp::ApplyWorkspaceEditParams {
21701                                label: None,
21702                                edit: lsp::WorkspaceEdit {
21703                                    changes: Some(
21704                                        [(
21705                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21706                                            vec![lsp::TextEdit {
21707                                                range: lsp::Range::new(
21708                                                    lsp::Position::new(0, 0),
21709                                                    lsp::Position::new(0, 0),
21710                                                ),
21711                                                new_text: "X".into(),
21712                                            }],
21713                                        )]
21714                                        .into_iter()
21715                                        .collect(),
21716                                    ),
21717                                    ..lsp::WorkspaceEdit::default()
21718                                },
21719                            },
21720                        )
21721                        .await
21722                        .into_response()
21723                        .unwrap();
21724                    Ok(Some(json!(null)))
21725                }
21726            }
21727        })
21728        .next()
21729        .await;
21730
21731    // Applying the code lens command returns a project transaction containing the edits
21732    // sent by the language server in its `workspaceEdit` request.
21733    let transaction = apply.await.unwrap();
21734    assert!(transaction.0.contains_key(&buffer));
21735    buffer.update(cx, |buffer, cx| {
21736        assert_eq!(buffer.text(), "Xa");
21737        buffer.undo(cx);
21738        assert_eq!(buffer.text(), "a");
21739    });
21740
21741    let actions_after_edits = cx
21742        .update_window(*workspace, |_, window, cx| {
21743            project.code_actions(&buffer, anchor..anchor, window, cx)
21744        })
21745        .unwrap()
21746        .await
21747        .unwrap();
21748    assert_eq!(
21749        actions, actions_after_edits,
21750        "For the same selection, same code lens actions should be returned"
21751    );
21752
21753    let _responses =
21754        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21755            panic!("No more code lens requests are expected");
21756        });
21757    editor.update_in(cx, |editor, window, cx| {
21758        editor.select_all(&SelectAll, window, cx);
21759    });
21760    cx.executor().run_until_parked();
21761    let new_actions = cx
21762        .update_window(*workspace, |_, window, cx| {
21763            project.code_actions(&buffer, anchor..anchor, window, cx)
21764        })
21765        .unwrap()
21766        .await
21767        .unwrap();
21768    assert_eq!(
21769        actions, new_actions,
21770        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21771    );
21772}
21773
21774#[gpui::test]
21775async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21776    init_test(cx, |_| {});
21777
21778    let fs = FakeFs::new(cx.executor());
21779    let main_text = r#"fn main() {
21780println!("1");
21781println!("2");
21782println!("3");
21783println!("4");
21784println!("5");
21785}"#;
21786    let lib_text = "mod foo {}";
21787    fs.insert_tree(
21788        path!("/a"),
21789        json!({
21790            "lib.rs": lib_text,
21791            "main.rs": main_text,
21792        }),
21793    )
21794    .await;
21795
21796    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21797    let (workspace, cx) =
21798        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21799    let worktree_id = workspace.update(cx, |workspace, cx| {
21800        workspace.project().update(cx, |project, cx| {
21801            project.worktrees(cx).next().unwrap().read(cx).id()
21802        })
21803    });
21804
21805    let expected_ranges = vec![
21806        Point::new(0, 0)..Point::new(0, 0),
21807        Point::new(1, 0)..Point::new(1, 1),
21808        Point::new(2, 0)..Point::new(2, 2),
21809        Point::new(3, 0)..Point::new(3, 3),
21810    ];
21811
21812    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21813    let editor_1 = workspace
21814        .update_in(cx, |workspace, window, cx| {
21815            workspace.open_path(
21816                (worktree_id, "main.rs"),
21817                Some(pane_1.downgrade()),
21818                true,
21819                window,
21820                cx,
21821            )
21822        })
21823        .unwrap()
21824        .await
21825        .downcast::<Editor>()
21826        .unwrap();
21827    pane_1.update(cx, |pane, cx| {
21828        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21829        open_editor.update(cx, |editor, cx| {
21830            assert_eq!(
21831                editor.display_text(cx),
21832                main_text,
21833                "Original main.rs text on initial open",
21834            );
21835            assert_eq!(
21836                editor
21837                    .selections
21838                    .all::<Point>(cx)
21839                    .into_iter()
21840                    .map(|s| s.range())
21841                    .collect::<Vec<_>>(),
21842                vec![Point::zero()..Point::zero()],
21843                "Default selections on initial open",
21844            );
21845        })
21846    });
21847    editor_1.update_in(cx, |editor, window, cx| {
21848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21849            s.select_ranges(expected_ranges.clone());
21850        });
21851    });
21852
21853    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21854        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21855    });
21856    let editor_2 = workspace
21857        .update_in(cx, |workspace, window, cx| {
21858            workspace.open_path(
21859                (worktree_id, "main.rs"),
21860                Some(pane_2.downgrade()),
21861                true,
21862                window,
21863                cx,
21864            )
21865        })
21866        .unwrap()
21867        .await
21868        .downcast::<Editor>()
21869        .unwrap();
21870    pane_2.update(cx, |pane, cx| {
21871        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21872        open_editor.update(cx, |editor, cx| {
21873            assert_eq!(
21874                editor.display_text(cx),
21875                main_text,
21876                "Original main.rs text on initial open in another panel",
21877            );
21878            assert_eq!(
21879                editor
21880                    .selections
21881                    .all::<Point>(cx)
21882                    .into_iter()
21883                    .map(|s| s.range())
21884                    .collect::<Vec<_>>(),
21885                vec![Point::zero()..Point::zero()],
21886                "Default selections on initial open in another panel",
21887            );
21888        })
21889    });
21890
21891    editor_2.update_in(cx, |editor, window, cx| {
21892        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21893    });
21894
21895    let _other_editor_1 = workspace
21896        .update_in(cx, |workspace, window, cx| {
21897            workspace.open_path(
21898                (worktree_id, "lib.rs"),
21899                Some(pane_1.downgrade()),
21900                true,
21901                window,
21902                cx,
21903            )
21904        })
21905        .unwrap()
21906        .await
21907        .downcast::<Editor>()
21908        .unwrap();
21909    pane_1
21910        .update_in(cx, |pane, window, cx| {
21911            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21912        })
21913        .await
21914        .unwrap();
21915    drop(editor_1);
21916    pane_1.update(cx, |pane, cx| {
21917        pane.active_item()
21918            .unwrap()
21919            .downcast::<Editor>()
21920            .unwrap()
21921            .update(cx, |editor, cx| {
21922                assert_eq!(
21923                    editor.display_text(cx),
21924                    lib_text,
21925                    "Other file should be open and active",
21926                );
21927            });
21928        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21929    });
21930
21931    let _other_editor_2 = workspace
21932        .update_in(cx, |workspace, window, cx| {
21933            workspace.open_path(
21934                (worktree_id, "lib.rs"),
21935                Some(pane_2.downgrade()),
21936                true,
21937                window,
21938                cx,
21939            )
21940        })
21941        .unwrap()
21942        .await
21943        .downcast::<Editor>()
21944        .unwrap();
21945    pane_2
21946        .update_in(cx, |pane, window, cx| {
21947            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21948        })
21949        .await
21950        .unwrap();
21951    drop(editor_2);
21952    pane_2.update(cx, |pane, cx| {
21953        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21954        open_editor.update(cx, |editor, cx| {
21955            assert_eq!(
21956                editor.display_text(cx),
21957                lib_text,
21958                "Other file should be open and active in another panel too",
21959            );
21960        });
21961        assert_eq!(
21962            pane.items().count(),
21963            1,
21964            "No other editors should be open in another pane",
21965        );
21966    });
21967
21968    let _editor_1_reopened = workspace
21969        .update_in(cx, |workspace, window, cx| {
21970            workspace.open_path(
21971                (worktree_id, "main.rs"),
21972                Some(pane_1.downgrade()),
21973                true,
21974                window,
21975                cx,
21976            )
21977        })
21978        .unwrap()
21979        .await
21980        .downcast::<Editor>()
21981        .unwrap();
21982    let _editor_2_reopened = workspace
21983        .update_in(cx, |workspace, window, cx| {
21984            workspace.open_path(
21985                (worktree_id, "main.rs"),
21986                Some(pane_2.downgrade()),
21987                true,
21988                window,
21989                cx,
21990            )
21991        })
21992        .unwrap()
21993        .await
21994        .downcast::<Editor>()
21995        .unwrap();
21996    pane_1.update(cx, |pane, cx| {
21997        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21998        open_editor.update(cx, |editor, cx| {
21999            assert_eq!(
22000                editor.display_text(cx),
22001                main_text,
22002                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22003            );
22004            assert_eq!(
22005                editor
22006                    .selections
22007                    .all::<Point>(cx)
22008                    .into_iter()
22009                    .map(|s| s.range())
22010                    .collect::<Vec<_>>(),
22011                expected_ranges,
22012                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22013            );
22014        })
22015    });
22016    pane_2.update(cx, |pane, cx| {
22017        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22018        open_editor.update(cx, |editor, cx| {
22019            assert_eq!(
22020                editor.display_text(cx),
22021                r#"fn main() {
22022⋯rintln!("1");
22023⋯intln!("2");
22024⋯ntln!("3");
22025println!("4");
22026println!("5");
22027}"#,
22028                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22029            );
22030            assert_eq!(
22031                editor
22032                    .selections
22033                    .all::<Point>(cx)
22034                    .into_iter()
22035                    .map(|s| s.range())
22036                    .collect::<Vec<_>>(),
22037                vec![Point::zero()..Point::zero()],
22038                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22039            );
22040        })
22041    });
22042}
22043
22044#[gpui::test]
22045async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22046    init_test(cx, |_| {});
22047
22048    let fs = FakeFs::new(cx.executor());
22049    let main_text = r#"fn main() {
22050println!("1");
22051println!("2");
22052println!("3");
22053println!("4");
22054println!("5");
22055}"#;
22056    let lib_text = "mod foo {}";
22057    fs.insert_tree(
22058        path!("/a"),
22059        json!({
22060            "lib.rs": lib_text,
22061            "main.rs": main_text,
22062        }),
22063    )
22064    .await;
22065
22066    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22067    let (workspace, cx) =
22068        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22069    let worktree_id = workspace.update(cx, |workspace, cx| {
22070        workspace.project().update(cx, |project, cx| {
22071            project.worktrees(cx).next().unwrap().read(cx).id()
22072        })
22073    });
22074
22075    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22076    let editor = workspace
22077        .update_in(cx, |workspace, window, cx| {
22078            workspace.open_path(
22079                (worktree_id, "main.rs"),
22080                Some(pane.downgrade()),
22081                true,
22082                window,
22083                cx,
22084            )
22085        })
22086        .unwrap()
22087        .await
22088        .downcast::<Editor>()
22089        .unwrap();
22090    pane.update(cx, |pane, cx| {
22091        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22092        open_editor.update(cx, |editor, cx| {
22093            assert_eq!(
22094                editor.display_text(cx),
22095                main_text,
22096                "Original main.rs text on initial open",
22097            );
22098        })
22099    });
22100    editor.update_in(cx, |editor, window, cx| {
22101        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22102    });
22103
22104    cx.update_global(|store: &mut SettingsStore, cx| {
22105        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22106            s.restore_on_file_reopen = Some(false);
22107        });
22108    });
22109    editor.update_in(cx, |editor, window, cx| {
22110        editor.fold_ranges(
22111            vec![
22112                Point::new(1, 0)..Point::new(1, 1),
22113                Point::new(2, 0)..Point::new(2, 2),
22114                Point::new(3, 0)..Point::new(3, 3),
22115            ],
22116            false,
22117            window,
22118            cx,
22119        );
22120    });
22121    pane.update_in(cx, |pane, window, cx| {
22122        pane.close_all_items(&CloseAllItems::default(), window, cx)
22123    })
22124    .await
22125    .unwrap();
22126    pane.update(cx, |pane, _| {
22127        assert!(pane.active_item().is_none());
22128    });
22129    cx.update_global(|store: &mut SettingsStore, cx| {
22130        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22131            s.restore_on_file_reopen = Some(true);
22132        });
22133    });
22134
22135    let _editor_reopened = workspace
22136        .update_in(cx, |workspace, window, cx| {
22137            workspace.open_path(
22138                (worktree_id, "main.rs"),
22139                Some(pane.downgrade()),
22140                true,
22141                window,
22142                cx,
22143            )
22144        })
22145        .unwrap()
22146        .await
22147        .downcast::<Editor>()
22148        .unwrap();
22149    pane.update(cx, |pane, cx| {
22150        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22151        open_editor.update(cx, |editor, cx| {
22152            assert_eq!(
22153                editor.display_text(cx),
22154                main_text,
22155                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22156            );
22157        })
22158    });
22159}
22160
22161#[gpui::test]
22162async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22163    struct EmptyModalView {
22164        focus_handle: gpui::FocusHandle,
22165    }
22166    impl EventEmitter<DismissEvent> for EmptyModalView {}
22167    impl Render for EmptyModalView {
22168        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22169            div()
22170        }
22171    }
22172    impl Focusable for EmptyModalView {
22173        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22174            self.focus_handle.clone()
22175        }
22176    }
22177    impl workspace::ModalView for EmptyModalView {}
22178    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22179        EmptyModalView {
22180            focus_handle: cx.focus_handle(),
22181        }
22182    }
22183
22184    init_test(cx, |_| {});
22185
22186    let fs = FakeFs::new(cx.executor());
22187    let project = Project::test(fs, [], cx).await;
22188    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22189    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22190    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22191    let editor = cx.new_window_entity(|window, cx| {
22192        Editor::new(
22193            EditorMode::full(),
22194            buffer,
22195            Some(project.clone()),
22196            window,
22197            cx,
22198        )
22199    });
22200    workspace
22201        .update(cx, |workspace, window, cx| {
22202            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22203        })
22204        .unwrap();
22205    editor.update_in(cx, |editor, window, cx| {
22206        editor.open_context_menu(&OpenContextMenu, window, cx);
22207        assert!(editor.mouse_context_menu.is_some());
22208    });
22209    workspace
22210        .update(cx, |workspace, window, cx| {
22211            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22212        })
22213        .unwrap();
22214    cx.read(|cx| {
22215        assert!(editor.read(cx).mouse_context_menu.is_none());
22216    });
22217}
22218
22219#[gpui::test]
22220async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22221    init_test(cx, |_| {});
22222
22223    let fs = FakeFs::new(cx.executor());
22224    fs.insert_file(path!("/file.html"), Default::default())
22225        .await;
22226
22227    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22228
22229    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22230    let html_language = Arc::new(Language::new(
22231        LanguageConfig {
22232            name: "HTML".into(),
22233            matcher: LanguageMatcher {
22234                path_suffixes: vec!["html".to_string()],
22235                ..LanguageMatcher::default()
22236            },
22237            brackets: BracketPairConfig {
22238                pairs: vec![BracketPair {
22239                    start: "<".into(),
22240                    end: ">".into(),
22241                    close: true,
22242                    ..Default::default()
22243                }],
22244                ..Default::default()
22245            },
22246            ..Default::default()
22247        },
22248        Some(tree_sitter_html::LANGUAGE.into()),
22249    ));
22250    language_registry.add(html_language);
22251    let mut fake_servers = language_registry.register_fake_lsp(
22252        "HTML",
22253        FakeLspAdapter {
22254            capabilities: lsp::ServerCapabilities {
22255                completion_provider: Some(lsp::CompletionOptions {
22256                    resolve_provider: Some(true),
22257                    ..Default::default()
22258                }),
22259                ..Default::default()
22260            },
22261            ..Default::default()
22262        },
22263    );
22264
22265    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22266    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22267
22268    let worktree_id = workspace
22269        .update(cx, |workspace, _window, cx| {
22270            workspace.project().update(cx, |project, cx| {
22271                project.worktrees(cx).next().unwrap().read(cx).id()
22272            })
22273        })
22274        .unwrap();
22275    project
22276        .update(cx, |project, cx| {
22277            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22278        })
22279        .await
22280        .unwrap();
22281    let editor = workspace
22282        .update(cx, |workspace, window, cx| {
22283            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22284        })
22285        .unwrap()
22286        .await
22287        .unwrap()
22288        .downcast::<Editor>()
22289        .unwrap();
22290
22291    let fake_server = fake_servers.next().await.unwrap();
22292    editor.update_in(cx, |editor, window, cx| {
22293        editor.set_text("<ad></ad>", window, cx);
22294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22295            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22296        });
22297        let Some((buffer, _)) = editor
22298            .buffer
22299            .read(cx)
22300            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22301        else {
22302            panic!("Failed to get buffer for selection position");
22303        };
22304        let buffer = buffer.read(cx);
22305        let buffer_id = buffer.remote_id();
22306        let opening_range =
22307            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22308        let closing_range =
22309            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22310        let mut linked_ranges = HashMap::default();
22311        linked_ranges.insert(
22312            buffer_id,
22313            vec![(opening_range.clone(), vec![closing_range.clone()])],
22314        );
22315        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22316    });
22317    let mut completion_handle =
22318        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22319            Ok(Some(lsp::CompletionResponse::Array(vec![
22320                lsp::CompletionItem {
22321                    label: "head".to_string(),
22322                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22323                        lsp::InsertReplaceEdit {
22324                            new_text: "head".to_string(),
22325                            insert: lsp::Range::new(
22326                                lsp::Position::new(0, 1),
22327                                lsp::Position::new(0, 3),
22328                            ),
22329                            replace: lsp::Range::new(
22330                                lsp::Position::new(0, 1),
22331                                lsp::Position::new(0, 3),
22332                            ),
22333                        },
22334                    )),
22335                    ..Default::default()
22336                },
22337            ])))
22338        });
22339    editor.update_in(cx, |editor, window, cx| {
22340        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22341    });
22342    cx.run_until_parked();
22343    completion_handle.next().await.unwrap();
22344    editor.update(cx, |editor, _| {
22345        assert!(
22346            editor.context_menu_visible(),
22347            "Completion menu should be visible"
22348        );
22349    });
22350    editor.update_in(cx, |editor, window, cx| {
22351        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22352    });
22353    cx.executor().run_until_parked();
22354    editor.update(cx, |editor, cx| {
22355        assert_eq!(editor.text(cx), "<head></head>");
22356    });
22357}
22358
22359#[gpui::test]
22360async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22361    init_test(cx, |_| {});
22362
22363    let fs = FakeFs::new(cx.executor());
22364    fs.insert_tree(
22365        path!("/root"),
22366        json!({
22367            "a": {
22368                "main.rs": "fn main() {}",
22369            },
22370            "foo": {
22371                "bar": {
22372                    "external_file.rs": "pub mod external {}",
22373                }
22374            }
22375        }),
22376    )
22377    .await;
22378
22379    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22380    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22381    language_registry.add(rust_lang());
22382    let _fake_servers = language_registry.register_fake_lsp(
22383        "Rust",
22384        FakeLspAdapter {
22385            ..FakeLspAdapter::default()
22386        },
22387    );
22388    let (workspace, cx) =
22389        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22390    let worktree_id = workspace.update(cx, |workspace, cx| {
22391        workspace.project().update(cx, |project, cx| {
22392            project.worktrees(cx).next().unwrap().read(cx).id()
22393        })
22394    });
22395
22396    let assert_language_servers_count =
22397        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22398            project.update(cx, |project, cx| {
22399                let current = project
22400                    .lsp_store()
22401                    .read(cx)
22402                    .as_local()
22403                    .unwrap()
22404                    .language_servers
22405                    .len();
22406                assert_eq!(expected, current, "{context}");
22407            });
22408        };
22409
22410    assert_language_servers_count(
22411        0,
22412        "No servers should be running before any file is open",
22413        cx,
22414    );
22415    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22416    let main_editor = workspace
22417        .update_in(cx, |workspace, window, cx| {
22418            workspace.open_path(
22419                (worktree_id, "main.rs"),
22420                Some(pane.downgrade()),
22421                true,
22422                window,
22423                cx,
22424            )
22425        })
22426        .unwrap()
22427        .await
22428        .downcast::<Editor>()
22429        .unwrap();
22430    pane.update(cx, |pane, cx| {
22431        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22432        open_editor.update(cx, |editor, cx| {
22433            assert_eq!(
22434                editor.display_text(cx),
22435                "fn main() {}",
22436                "Original main.rs text on initial open",
22437            );
22438        });
22439        assert_eq!(open_editor, main_editor);
22440    });
22441    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22442
22443    let external_editor = workspace
22444        .update_in(cx, |workspace, window, cx| {
22445            workspace.open_abs_path(
22446                PathBuf::from("/root/foo/bar/external_file.rs"),
22447                OpenOptions::default(),
22448                window,
22449                cx,
22450            )
22451        })
22452        .await
22453        .expect("opening external file")
22454        .downcast::<Editor>()
22455        .expect("downcasted external file's open element to editor");
22456    pane.update(cx, |pane, cx| {
22457        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22458        open_editor.update(cx, |editor, cx| {
22459            assert_eq!(
22460                editor.display_text(cx),
22461                "pub mod external {}",
22462                "External file is open now",
22463            );
22464        });
22465        assert_eq!(open_editor, external_editor);
22466    });
22467    assert_language_servers_count(
22468        1,
22469        "Second, external, *.rs file should join the existing server",
22470        cx,
22471    );
22472
22473    pane.update_in(cx, |pane, window, cx| {
22474        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22475    })
22476    .await
22477    .unwrap();
22478    pane.update_in(cx, |pane, window, cx| {
22479        pane.navigate_backward(window, cx);
22480    });
22481    cx.run_until_parked();
22482    pane.update(cx, |pane, cx| {
22483        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22484        open_editor.update(cx, |editor, cx| {
22485            assert_eq!(
22486                editor.display_text(cx),
22487                "pub mod external {}",
22488                "External file is open now",
22489            );
22490        });
22491    });
22492    assert_language_servers_count(
22493        1,
22494        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22495        cx,
22496    );
22497
22498    cx.update(|_, cx| {
22499        workspace::reload(cx);
22500    });
22501    assert_language_servers_count(
22502        1,
22503        "After reloading the worktree with local and external files opened, only one project should be started",
22504        cx,
22505    );
22506}
22507
22508#[gpui::test]
22509async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22510    init_test(cx, |_| {});
22511
22512    let mut cx = EditorTestContext::new(cx).await;
22513    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22514    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22515
22516    // test cursor move to start of each line on tab
22517    // for `if`, `elif`, `else`, `while`, `with` and `for`
22518    cx.set_state(indoc! {"
22519        def main():
22520        ˇ    for item in items:
22521        ˇ        while item.active:
22522        ˇ            if item.value > 10:
22523        ˇ                continue
22524        ˇ            elif item.value < 0:
22525        ˇ                break
22526        ˇ            else:
22527        ˇ                with item.context() as ctx:
22528        ˇ                    yield count
22529        ˇ        else:
22530        ˇ            log('while else')
22531        ˇ    else:
22532        ˇ        log('for else')
22533    "});
22534    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22535    cx.assert_editor_state(indoc! {"
22536        def main():
22537            ˇfor item in items:
22538                ˇwhile item.active:
22539                    ˇif item.value > 10:
22540                        ˇcontinue
22541                    ˇelif item.value < 0:
22542                        ˇbreak
22543                    ˇelse:
22544                        ˇwith item.context() as ctx:
22545                            ˇyield count
22546                ˇelse:
22547                    ˇlog('while else')
22548            ˇelse:
22549                ˇlog('for else')
22550    "});
22551    // test relative indent is preserved when tab
22552    // for `if`, `elif`, `else`, `while`, `with` and `for`
22553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22554    cx.assert_editor_state(indoc! {"
22555        def main():
22556                ˇfor item in items:
22557                    ˇwhile item.active:
22558                        ˇif item.value > 10:
22559                            ˇcontinue
22560                        ˇelif item.value < 0:
22561                            ˇbreak
22562                        ˇelse:
22563                            ˇwith item.context() as ctx:
22564                                ˇyield count
22565                    ˇelse:
22566                        ˇlog('while else')
22567                ˇelse:
22568                    ˇlog('for else')
22569    "});
22570
22571    // test cursor move to start of each line on tab
22572    // for `try`, `except`, `else`, `finally`, `match` and `def`
22573    cx.set_state(indoc! {"
22574        def main():
22575        ˇ    try:
22576        ˇ        fetch()
22577        ˇ    except ValueError:
22578        ˇ        handle_error()
22579        ˇ    else:
22580        ˇ        match value:
22581        ˇ            case _:
22582        ˇ    finally:
22583        ˇ        def status():
22584        ˇ            return 0
22585    "});
22586    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22587    cx.assert_editor_state(indoc! {"
22588        def main():
22589            ˇtry:
22590                ˇfetch()
22591            ˇexcept ValueError:
22592                ˇhandle_error()
22593            ˇelse:
22594                ˇmatch value:
22595                    ˇcase _:
22596            ˇfinally:
22597                ˇdef status():
22598                    ˇreturn 0
22599    "});
22600    // test relative indent is preserved when tab
22601    // for `try`, `except`, `else`, `finally`, `match` and `def`
22602    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22603    cx.assert_editor_state(indoc! {"
22604        def main():
22605                ˇtry:
22606                    ˇfetch()
22607                ˇexcept ValueError:
22608                    ˇhandle_error()
22609                ˇelse:
22610                    ˇmatch value:
22611                        ˇcase _:
22612                ˇfinally:
22613                    ˇdef status():
22614                        ˇreturn 0
22615    "});
22616}
22617
22618#[gpui::test]
22619async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22620    init_test(cx, |_| {});
22621
22622    let mut cx = EditorTestContext::new(cx).await;
22623    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22624    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22625
22626    // test `else` auto outdents when typed inside `if` block
22627    cx.set_state(indoc! {"
22628        def main():
22629            if i == 2:
22630                return
22631                ˇ
22632    "});
22633    cx.update_editor(|editor, window, cx| {
22634        editor.handle_input("else:", window, cx);
22635    });
22636    cx.assert_editor_state(indoc! {"
22637        def main():
22638            if i == 2:
22639                return
22640            else:ˇ
22641    "});
22642
22643    // test `except` auto outdents when typed inside `try` block
22644    cx.set_state(indoc! {"
22645        def main():
22646            try:
22647                i = 2
22648                ˇ
22649    "});
22650    cx.update_editor(|editor, window, cx| {
22651        editor.handle_input("except:", window, cx);
22652    });
22653    cx.assert_editor_state(indoc! {"
22654        def main():
22655            try:
22656                i = 2
22657            except:ˇ
22658    "});
22659
22660    // test `else` auto outdents when typed inside `except` block
22661    cx.set_state(indoc! {"
22662        def main():
22663            try:
22664                i = 2
22665            except:
22666                j = 2
22667                ˇ
22668    "});
22669    cx.update_editor(|editor, window, cx| {
22670        editor.handle_input("else:", window, cx);
22671    });
22672    cx.assert_editor_state(indoc! {"
22673        def main():
22674            try:
22675                i = 2
22676            except:
22677                j = 2
22678            else:ˇ
22679    "});
22680
22681    // test `finally` auto outdents when typed inside `else` block
22682    cx.set_state(indoc! {"
22683        def main():
22684            try:
22685                i = 2
22686            except:
22687                j = 2
22688            else:
22689                k = 2
22690                ˇ
22691    "});
22692    cx.update_editor(|editor, window, cx| {
22693        editor.handle_input("finally:", window, cx);
22694    });
22695    cx.assert_editor_state(indoc! {"
22696        def main():
22697            try:
22698                i = 2
22699            except:
22700                j = 2
22701            else:
22702                k = 2
22703            finally:ˇ
22704    "});
22705
22706    // test `else` does not outdents when typed inside `except` block right after for block
22707    cx.set_state(indoc! {"
22708        def main():
22709            try:
22710                i = 2
22711            except:
22712                for i in range(n):
22713                    pass
22714                ˇ
22715    "});
22716    cx.update_editor(|editor, window, cx| {
22717        editor.handle_input("else:", window, cx);
22718    });
22719    cx.assert_editor_state(indoc! {"
22720        def main():
22721            try:
22722                i = 2
22723            except:
22724                for i in range(n):
22725                    pass
22726                else:ˇ
22727    "});
22728
22729    // test `finally` auto outdents when typed inside `else` block right after for block
22730    cx.set_state(indoc! {"
22731        def main():
22732            try:
22733                i = 2
22734            except:
22735                j = 2
22736            else:
22737                for i in range(n):
22738                    pass
22739                ˇ
22740    "});
22741    cx.update_editor(|editor, window, cx| {
22742        editor.handle_input("finally:", window, cx);
22743    });
22744    cx.assert_editor_state(indoc! {"
22745        def main():
22746            try:
22747                i = 2
22748            except:
22749                j = 2
22750            else:
22751                for i in range(n):
22752                    pass
22753            finally:ˇ
22754    "});
22755
22756    // test `except` outdents to inner "try" block
22757    cx.set_state(indoc! {"
22758        def main():
22759            try:
22760                i = 2
22761                if i == 2:
22762                    try:
22763                        i = 3
22764                        ˇ
22765    "});
22766    cx.update_editor(|editor, window, cx| {
22767        editor.handle_input("except:", window, cx);
22768    });
22769    cx.assert_editor_state(indoc! {"
22770        def main():
22771            try:
22772                i = 2
22773                if i == 2:
22774                    try:
22775                        i = 3
22776                    except:ˇ
22777    "});
22778
22779    // test `except` outdents to outer "try" block
22780    cx.set_state(indoc! {"
22781        def main():
22782            try:
22783                i = 2
22784                if i == 2:
22785                    try:
22786                        i = 3
22787                ˇ
22788    "});
22789    cx.update_editor(|editor, window, cx| {
22790        editor.handle_input("except:", window, cx);
22791    });
22792    cx.assert_editor_state(indoc! {"
22793        def main():
22794            try:
22795                i = 2
22796                if i == 2:
22797                    try:
22798                        i = 3
22799            except:ˇ
22800    "});
22801
22802    // test `else` stays at correct indent when typed after `for` block
22803    cx.set_state(indoc! {"
22804        def main():
22805            for i in range(10):
22806                if i == 3:
22807                    break
22808            ˇ
22809    "});
22810    cx.update_editor(|editor, window, cx| {
22811        editor.handle_input("else:", window, cx);
22812    });
22813    cx.assert_editor_state(indoc! {"
22814        def main():
22815            for i in range(10):
22816                if i == 3:
22817                    break
22818            else:ˇ
22819    "});
22820
22821    // test does not outdent on typing after line with square brackets
22822    cx.set_state(indoc! {"
22823        def f() -> list[str]:
22824            ˇ
22825    "});
22826    cx.update_editor(|editor, window, cx| {
22827        editor.handle_input("a", window, cx);
22828    });
22829    cx.assert_editor_state(indoc! {"
22830        def f() -> list[str]:
2283122832    "});
22833
22834    // test does not outdent on typing : after case keyword
22835    cx.set_state(indoc! {"
22836        match 1:
22837            caseˇ
22838    "});
22839    cx.update_editor(|editor, window, cx| {
22840        editor.handle_input(":", window, cx);
22841    });
22842    cx.assert_editor_state(indoc! {"
22843        match 1:
22844            case:ˇ
22845    "});
22846}
22847
22848#[gpui::test]
22849async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22850    init_test(cx, |_| {});
22851    update_test_language_settings(cx, |settings| {
22852        settings.defaults.extend_comment_on_newline = Some(false);
22853    });
22854    let mut cx = EditorTestContext::new(cx).await;
22855    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22856    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22857
22858    // test correct indent after newline on comment
22859    cx.set_state(indoc! {"
22860        # COMMENT:ˇ
22861    "});
22862    cx.update_editor(|editor, window, cx| {
22863        editor.newline(&Newline, window, cx);
22864    });
22865    cx.assert_editor_state(indoc! {"
22866        # COMMENT:
22867        ˇ
22868    "});
22869
22870    // test correct indent after newline in brackets
22871    cx.set_state(indoc! {"
22872        {ˇ}
22873    "});
22874    cx.update_editor(|editor, window, cx| {
22875        editor.newline(&Newline, window, cx);
22876    });
22877    cx.run_until_parked();
22878    cx.assert_editor_state(indoc! {"
22879        {
22880            ˇ
22881        }
22882    "});
22883
22884    cx.set_state(indoc! {"
22885        (ˇ)
22886    "});
22887    cx.update_editor(|editor, window, cx| {
22888        editor.newline(&Newline, window, cx);
22889    });
22890    cx.run_until_parked();
22891    cx.assert_editor_state(indoc! {"
22892        (
22893            ˇ
22894        )
22895    "});
22896
22897    // do not indent after empty lists or dictionaries
22898    cx.set_state(indoc! {"
22899        a = []ˇ
22900    "});
22901    cx.update_editor(|editor, window, cx| {
22902        editor.newline(&Newline, window, cx);
22903    });
22904    cx.run_until_parked();
22905    cx.assert_editor_state(indoc! {"
22906        a = []
22907        ˇ
22908    "});
22909}
22910
22911#[gpui::test]
22912async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22913    init_test(cx, |_| {});
22914
22915    let mut cx = EditorTestContext::new(cx).await;
22916    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22917    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22918
22919    // test cursor move to start of each line on tab
22920    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22921    cx.set_state(indoc! {"
22922        function main() {
22923        ˇ    for item in $items; do
22924        ˇ        while [ -n \"$item\" ]; do
22925        ˇ            if [ \"$value\" -gt 10 ]; then
22926        ˇ                continue
22927        ˇ            elif [ \"$value\" -lt 0 ]; then
22928        ˇ                break
22929        ˇ            else
22930        ˇ                echo \"$item\"
22931        ˇ            fi
22932        ˇ        done
22933        ˇ    done
22934        ˇ}
22935    "});
22936    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22937    cx.assert_editor_state(indoc! {"
22938        function main() {
22939            ˇfor item in $items; do
22940                ˇwhile [ -n \"$item\" ]; do
22941                    ˇif [ \"$value\" -gt 10 ]; then
22942                        ˇcontinue
22943                    ˇelif [ \"$value\" -lt 0 ]; then
22944                        ˇbreak
22945                    ˇelse
22946                        ˇecho \"$item\"
22947                    ˇfi
22948                ˇdone
22949            ˇdone
22950        ˇ}
22951    "});
22952    // test relative indent is preserved when tab
22953    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22954    cx.assert_editor_state(indoc! {"
22955        function main() {
22956                ˇfor item in $items; do
22957                    ˇwhile [ -n \"$item\" ]; do
22958                        ˇif [ \"$value\" -gt 10 ]; then
22959                            ˇcontinue
22960                        ˇelif [ \"$value\" -lt 0 ]; then
22961                            ˇbreak
22962                        ˇelse
22963                            ˇecho \"$item\"
22964                        ˇfi
22965                    ˇdone
22966                ˇdone
22967            ˇ}
22968    "});
22969
22970    // test cursor move to start of each line on tab
22971    // for `case` statement with patterns
22972    cx.set_state(indoc! {"
22973        function handle() {
22974        ˇ    case \"$1\" in
22975        ˇ        start)
22976        ˇ            echo \"a\"
22977        ˇ            ;;
22978        ˇ        stop)
22979        ˇ            echo \"b\"
22980        ˇ            ;;
22981        ˇ        *)
22982        ˇ            echo \"c\"
22983        ˇ            ;;
22984        ˇ    esac
22985        ˇ}
22986    "});
22987    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22988    cx.assert_editor_state(indoc! {"
22989        function handle() {
22990            ˇcase \"$1\" in
22991                ˇstart)
22992                    ˇecho \"a\"
22993                    ˇ;;
22994                ˇstop)
22995                    ˇecho \"b\"
22996                    ˇ;;
22997                ˇ*)
22998                    ˇecho \"c\"
22999                    ˇ;;
23000            ˇesac
23001        ˇ}
23002    "});
23003}
23004
23005#[gpui::test]
23006async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23007    init_test(cx, |_| {});
23008
23009    let mut cx = EditorTestContext::new(cx).await;
23010    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23011    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23012
23013    // test indents on comment insert
23014    cx.set_state(indoc! {"
23015        function main() {
23016        ˇ    for item in $items; do
23017        ˇ        while [ -n \"$item\" ]; do
23018        ˇ            if [ \"$value\" -gt 10 ]; then
23019        ˇ                continue
23020        ˇ            elif [ \"$value\" -lt 0 ]; then
23021        ˇ                break
23022        ˇ            else
23023        ˇ                echo \"$item\"
23024        ˇ            fi
23025        ˇ        done
23026        ˇ    done
23027        ˇ}
23028    "});
23029    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23030    cx.assert_editor_state(indoc! {"
23031        function main() {
23032        #ˇ    for item in $items; do
23033        #ˇ        while [ -n \"$item\" ]; do
23034        #ˇ            if [ \"$value\" -gt 10 ]; then
23035        #ˇ                continue
23036        #ˇ            elif [ \"$value\" -lt 0 ]; then
23037        #ˇ                break
23038        #ˇ            else
23039        #ˇ                echo \"$item\"
23040        #ˇ            fi
23041        #ˇ        done
23042        #ˇ    done
23043        #ˇ}
23044    "});
23045}
23046
23047#[gpui::test]
23048async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23049    init_test(cx, |_| {});
23050
23051    let mut cx = EditorTestContext::new(cx).await;
23052    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23053    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23054
23055    // test `else` auto outdents when typed inside `if` block
23056    cx.set_state(indoc! {"
23057        if [ \"$1\" = \"test\" ]; then
23058            echo \"foo bar\"
23059            ˇ
23060    "});
23061    cx.update_editor(|editor, window, cx| {
23062        editor.handle_input("else", window, cx);
23063    });
23064    cx.assert_editor_state(indoc! {"
23065        if [ \"$1\" = \"test\" ]; then
23066            echo \"foo bar\"
23067        elseˇ
23068    "});
23069
23070    // test `elif` auto outdents when typed inside `if` block
23071    cx.set_state(indoc! {"
23072        if [ \"$1\" = \"test\" ]; then
23073            echo \"foo bar\"
23074            ˇ
23075    "});
23076    cx.update_editor(|editor, window, cx| {
23077        editor.handle_input("elif", window, cx);
23078    });
23079    cx.assert_editor_state(indoc! {"
23080        if [ \"$1\" = \"test\" ]; then
23081            echo \"foo bar\"
23082        elifˇ
23083    "});
23084
23085    // test `fi` auto outdents when typed inside `else` block
23086    cx.set_state(indoc! {"
23087        if [ \"$1\" = \"test\" ]; then
23088            echo \"foo bar\"
23089        else
23090            echo \"bar baz\"
23091            ˇ
23092    "});
23093    cx.update_editor(|editor, window, cx| {
23094        editor.handle_input("fi", window, cx);
23095    });
23096    cx.assert_editor_state(indoc! {"
23097        if [ \"$1\" = \"test\" ]; then
23098            echo \"foo bar\"
23099        else
23100            echo \"bar baz\"
23101        fiˇ
23102    "});
23103
23104    // test `done` auto outdents when typed inside `while` block
23105    cx.set_state(indoc! {"
23106        while read line; do
23107            echo \"$line\"
23108            ˇ
23109    "});
23110    cx.update_editor(|editor, window, cx| {
23111        editor.handle_input("done", window, cx);
23112    });
23113    cx.assert_editor_state(indoc! {"
23114        while read line; do
23115            echo \"$line\"
23116        doneˇ
23117    "});
23118
23119    // test `done` auto outdents when typed inside `for` block
23120    cx.set_state(indoc! {"
23121        for file in *.txt; do
23122            cat \"$file\"
23123            ˇ
23124    "});
23125    cx.update_editor(|editor, window, cx| {
23126        editor.handle_input("done", window, cx);
23127    });
23128    cx.assert_editor_state(indoc! {"
23129        for file in *.txt; do
23130            cat \"$file\"
23131        doneˇ
23132    "});
23133
23134    // test `esac` auto outdents when typed inside `case` block
23135    cx.set_state(indoc! {"
23136        case \"$1\" in
23137            start)
23138                echo \"foo bar\"
23139                ;;
23140            stop)
23141                echo \"bar baz\"
23142                ;;
23143            ˇ
23144    "});
23145    cx.update_editor(|editor, window, cx| {
23146        editor.handle_input("esac", window, cx);
23147    });
23148    cx.assert_editor_state(indoc! {"
23149        case \"$1\" in
23150            start)
23151                echo \"foo bar\"
23152                ;;
23153            stop)
23154                echo \"bar baz\"
23155                ;;
23156        esacˇ
23157    "});
23158
23159    // test `*)` auto outdents when typed inside `case` block
23160    cx.set_state(indoc! {"
23161        case \"$1\" in
23162            start)
23163                echo \"foo bar\"
23164                ;;
23165                ˇ
23166    "});
23167    cx.update_editor(|editor, window, cx| {
23168        editor.handle_input("*)", window, cx);
23169    });
23170    cx.assert_editor_state(indoc! {"
23171        case \"$1\" in
23172            start)
23173                echo \"foo bar\"
23174                ;;
23175            *)ˇ
23176    "});
23177
23178    // test `fi` outdents to correct level with nested if blocks
23179    cx.set_state(indoc! {"
23180        if [ \"$1\" = \"test\" ]; then
23181            echo \"outer if\"
23182            if [ \"$2\" = \"debug\" ]; then
23183                echo \"inner if\"
23184                ˇ
23185    "});
23186    cx.update_editor(|editor, window, cx| {
23187        editor.handle_input("fi", window, cx);
23188    });
23189    cx.assert_editor_state(indoc! {"
23190        if [ \"$1\" = \"test\" ]; then
23191            echo \"outer if\"
23192            if [ \"$2\" = \"debug\" ]; then
23193                echo \"inner if\"
23194            fiˇ
23195    "});
23196}
23197
23198#[gpui::test]
23199async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23200    init_test(cx, |_| {});
23201    update_test_language_settings(cx, |settings| {
23202        settings.defaults.extend_comment_on_newline = Some(false);
23203    });
23204    let mut cx = EditorTestContext::new(cx).await;
23205    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23206    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23207
23208    // test correct indent after newline on comment
23209    cx.set_state(indoc! {"
23210        # COMMENT:ˇ
23211    "});
23212    cx.update_editor(|editor, window, cx| {
23213        editor.newline(&Newline, window, cx);
23214    });
23215    cx.assert_editor_state(indoc! {"
23216        # COMMENT:
23217        ˇ
23218    "});
23219
23220    // test correct indent after newline after `then`
23221    cx.set_state(indoc! {"
23222
23223        if [ \"$1\" = \"test\" ]; thenˇ
23224    "});
23225    cx.update_editor(|editor, window, cx| {
23226        editor.newline(&Newline, window, cx);
23227    });
23228    cx.run_until_parked();
23229    cx.assert_editor_state(indoc! {"
23230
23231        if [ \"$1\" = \"test\" ]; then
23232            ˇ
23233    "});
23234
23235    // test correct indent after newline after `else`
23236    cx.set_state(indoc! {"
23237        if [ \"$1\" = \"test\" ]; then
23238        elseˇ
23239    "});
23240    cx.update_editor(|editor, window, cx| {
23241        editor.newline(&Newline, window, cx);
23242    });
23243    cx.run_until_parked();
23244    cx.assert_editor_state(indoc! {"
23245        if [ \"$1\" = \"test\" ]; then
23246        else
23247            ˇ
23248    "});
23249
23250    // test correct indent after newline after `elif`
23251    cx.set_state(indoc! {"
23252        if [ \"$1\" = \"test\" ]; then
23253        elifˇ
23254    "});
23255    cx.update_editor(|editor, window, cx| {
23256        editor.newline(&Newline, window, cx);
23257    });
23258    cx.run_until_parked();
23259    cx.assert_editor_state(indoc! {"
23260        if [ \"$1\" = \"test\" ]; then
23261        elif
23262            ˇ
23263    "});
23264
23265    // test correct indent after newline after `do`
23266    cx.set_state(indoc! {"
23267        for file in *.txt; doˇ
23268    "});
23269    cx.update_editor(|editor, window, cx| {
23270        editor.newline(&Newline, window, cx);
23271    });
23272    cx.run_until_parked();
23273    cx.assert_editor_state(indoc! {"
23274        for file in *.txt; do
23275            ˇ
23276    "});
23277
23278    // test correct indent after newline after case pattern
23279    cx.set_state(indoc! {"
23280        case \"$1\" in
23281            start)ˇ
23282    "});
23283    cx.update_editor(|editor, window, cx| {
23284        editor.newline(&Newline, window, cx);
23285    });
23286    cx.run_until_parked();
23287    cx.assert_editor_state(indoc! {"
23288        case \"$1\" in
23289            start)
23290                ˇ
23291    "});
23292
23293    // test correct indent after newline after case pattern
23294    cx.set_state(indoc! {"
23295        case \"$1\" in
23296            start)
23297                ;;
23298            *)ˇ
23299    "});
23300    cx.update_editor(|editor, window, cx| {
23301        editor.newline(&Newline, window, cx);
23302    });
23303    cx.run_until_parked();
23304    cx.assert_editor_state(indoc! {"
23305        case \"$1\" in
23306            start)
23307                ;;
23308            *)
23309                ˇ
23310    "});
23311
23312    // test correct indent after newline after function opening brace
23313    cx.set_state(indoc! {"
23314        function test() {ˇ}
23315    "});
23316    cx.update_editor(|editor, window, cx| {
23317        editor.newline(&Newline, window, cx);
23318    });
23319    cx.run_until_parked();
23320    cx.assert_editor_state(indoc! {"
23321        function test() {
23322            ˇ
23323        }
23324    "});
23325
23326    // test no extra indent after semicolon on same line
23327    cx.set_state(indoc! {"
23328        echo \"test\"23329    "});
23330    cx.update_editor(|editor, window, cx| {
23331        editor.newline(&Newline, window, cx);
23332    });
23333    cx.run_until_parked();
23334    cx.assert_editor_state(indoc! {"
23335        echo \"test\";
23336        ˇ
23337    "});
23338}
23339
23340fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23341    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23342    point..point
23343}
23344
23345#[track_caller]
23346fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23347    let (text, ranges) = marked_text_ranges(marked_text, true);
23348    assert_eq!(editor.text(cx), text);
23349    assert_eq!(
23350        editor.selections.ranges(cx),
23351        ranges,
23352        "Assert selections are {}",
23353        marked_text
23354    );
23355}
23356
23357pub fn handle_signature_help_request(
23358    cx: &mut EditorLspTestContext,
23359    mocked_response: lsp::SignatureHelp,
23360) -> impl Future<Output = ()> + use<> {
23361    let mut request =
23362        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23363            let mocked_response = mocked_response.clone();
23364            async move { Ok(Some(mocked_response)) }
23365        });
23366
23367    async move {
23368        request.next().await;
23369    }
23370}
23371
23372#[track_caller]
23373pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23374    cx.update_editor(|editor, _, _| {
23375        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23376            let entries = menu.entries.borrow();
23377            let entries = entries
23378                .iter()
23379                .map(|entry| entry.string.as_str())
23380                .collect::<Vec<_>>();
23381            assert_eq!(entries, expected);
23382        } else {
23383            panic!("Expected completions menu");
23384        }
23385    });
23386}
23387
23388/// Handle completion request passing a marked string specifying where the completion
23389/// should be triggered from using '|' character, what range should be replaced, and what completions
23390/// should be returned using '<' and '>' to delimit the range.
23391///
23392/// Also see `handle_completion_request_with_insert_and_replace`.
23393#[track_caller]
23394pub fn handle_completion_request(
23395    marked_string: &str,
23396    completions: Vec<&'static str>,
23397    is_incomplete: bool,
23398    counter: Arc<AtomicUsize>,
23399    cx: &mut EditorLspTestContext,
23400) -> impl Future<Output = ()> {
23401    let complete_from_marker: TextRangeMarker = '|'.into();
23402    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23403    let (_, mut marked_ranges) = marked_text_ranges_by(
23404        marked_string,
23405        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23406    );
23407
23408    let complete_from_position =
23409        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23410    let replace_range =
23411        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23412
23413    let mut request =
23414        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23415            let completions = completions.clone();
23416            counter.fetch_add(1, atomic::Ordering::Release);
23417            async move {
23418                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23419                assert_eq!(
23420                    params.text_document_position.position,
23421                    complete_from_position
23422                );
23423                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23424                    is_incomplete: is_incomplete,
23425                    item_defaults: None,
23426                    items: completions
23427                        .iter()
23428                        .map(|completion_text| lsp::CompletionItem {
23429                            label: completion_text.to_string(),
23430                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23431                                range: replace_range,
23432                                new_text: completion_text.to_string(),
23433                            })),
23434                            ..Default::default()
23435                        })
23436                        .collect(),
23437                })))
23438            }
23439        });
23440
23441    async move {
23442        request.next().await;
23443    }
23444}
23445
23446/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23447/// given instead, which also contains an `insert` range.
23448///
23449/// This function uses markers to define ranges:
23450/// - `|` marks the cursor position
23451/// - `<>` marks the replace range
23452/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23453pub fn handle_completion_request_with_insert_and_replace(
23454    cx: &mut EditorLspTestContext,
23455    marked_string: &str,
23456    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23457    counter: Arc<AtomicUsize>,
23458) -> impl Future<Output = ()> {
23459    let complete_from_marker: TextRangeMarker = '|'.into();
23460    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23461    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23462
23463    let (_, mut marked_ranges) = marked_text_ranges_by(
23464        marked_string,
23465        vec![
23466            complete_from_marker.clone(),
23467            replace_range_marker.clone(),
23468            insert_range_marker.clone(),
23469        ],
23470    );
23471
23472    let complete_from_position =
23473        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23474    let replace_range =
23475        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23476
23477    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23478        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23479        _ => lsp::Range {
23480            start: replace_range.start,
23481            end: complete_from_position,
23482        },
23483    };
23484
23485    let mut request =
23486        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23487            let completions = completions.clone();
23488            counter.fetch_add(1, atomic::Ordering::Release);
23489            async move {
23490                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23491                assert_eq!(
23492                    params.text_document_position.position, complete_from_position,
23493                    "marker `|` position doesn't match",
23494                );
23495                Ok(Some(lsp::CompletionResponse::Array(
23496                    completions
23497                        .iter()
23498                        .map(|(label, new_text)| lsp::CompletionItem {
23499                            label: label.to_string(),
23500                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23501                                lsp::InsertReplaceEdit {
23502                                    insert: insert_range,
23503                                    replace: replace_range,
23504                                    new_text: new_text.to_string(),
23505                                },
23506                            )),
23507                            ..Default::default()
23508                        })
23509                        .collect(),
23510                )))
23511            }
23512        });
23513
23514    async move {
23515        request.next().await;
23516    }
23517}
23518
23519fn handle_resolve_completion_request(
23520    cx: &mut EditorLspTestContext,
23521    edits: Option<Vec<(&'static str, &'static str)>>,
23522) -> impl Future<Output = ()> {
23523    let edits = edits.map(|edits| {
23524        edits
23525            .iter()
23526            .map(|(marked_string, new_text)| {
23527                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23528                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23529                lsp::TextEdit::new(replace_range, new_text.to_string())
23530            })
23531            .collect::<Vec<_>>()
23532    });
23533
23534    let mut request =
23535        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23536            let edits = edits.clone();
23537            async move {
23538                Ok(lsp::CompletionItem {
23539                    additional_text_edits: edits,
23540                    ..Default::default()
23541                })
23542            }
23543        });
23544
23545    async move {
23546        request.next().await;
23547    }
23548}
23549
23550pub(crate) fn update_test_language_settings(
23551    cx: &mut TestAppContext,
23552    f: impl Fn(&mut AllLanguageSettingsContent),
23553) {
23554    cx.update(|cx| {
23555        SettingsStore::update_global(cx, |store, cx| {
23556            store.update_user_settings::<AllLanguageSettings>(cx, f);
23557        });
23558    });
23559}
23560
23561pub(crate) fn update_test_project_settings(
23562    cx: &mut TestAppContext,
23563    f: impl Fn(&mut ProjectSettings),
23564) {
23565    cx.update(|cx| {
23566        SettingsStore::update_global(cx, |store, cx| {
23567            store.update_user_settings::<ProjectSettings>(cx, f);
23568        });
23569    });
23570}
23571
23572pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23573    cx.update(|cx| {
23574        assets::Assets.load_test_fonts(cx);
23575        let store = SettingsStore::test(cx);
23576        cx.set_global(store);
23577        theme::init(theme::LoadThemes::JustBase, cx);
23578        release_channel::init(SemanticVersion::default(), cx);
23579        client::init_settings(cx);
23580        language::init(cx);
23581        Project::init_settings(cx);
23582        workspace::init_settings(cx);
23583        crate::init(cx);
23584    });
23585    zlog::init_test();
23586    update_test_language_settings(cx, f);
23587}
23588
23589#[track_caller]
23590fn assert_hunk_revert(
23591    not_reverted_text_with_selections: &str,
23592    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23593    expected_reverted_text_with_selections: &str,
23594    base_text: &str,
23595    cx: &mut EditorLspTestContext,
23596) {
23597    cx.set_state(not_reverted_text_with_selections);
23598    cx.set_head_text(base_text);
23599    cx.executor().run_until_parked();
23600
23601    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23602        let snapshot = editor.snapshot(window, cx);
23603        let reverted_hunk_statuses = snapshot
23604            .buffer_snapshot
23605            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23606            .map(|hunk| hunk.status().kind)
23607            .collect::<Vec<_>>();
23608
23609        editor.git_restore(&Default::default(), window, cx);
23610        reverted_hunk_statuses
23611    });
23612    cx.executor().run_until_parked();
23613    cx.assert_editor_state(expected_reverted_text_with_selections);
23614    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23615}
23616
23617#[gpui::test(iterations = 10)]
23618async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23619    init_test(cx, |_| {});
23620
23621    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23622    let counter = diagnostic_requests.clone();
23623
23624    let fs = FakeFs::new(cx.executor());
23625    fs.insert_tree(
23626        path!("/a"),
23627        json!({
23628            "first.rs": "fn main() { let a = 5; }",
23629            "second.rs": "// Test file",
23630        }),
23631    )
23632    .await;
23633
23634    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23635    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23636    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23637
23638    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23639    language_registry.add(rust_lang());
23640    let mut fake_servers = language_registry.register_fake_lsp(
23641        "Rust",
23642        FakeLspAdapter {
23643            capabilities: lsp::ServerCapabilities {
23644                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23645                    lsp::DiagnosticOptions {
23646                        identifier: None,
23647                        inter_file_dependencies: true,
23648                        workspace_diagnostics: true,
23649                        work_done_progress_options: Default::default(),
23650                    },
23651                )),
23652                ..Default::default()
23653            },
23654            ..Default::default()
23655        },
23656    );
23657
23658    let editor = workspace
23659        .update(cx, |workspace, window, cx| {
23660            workspace.open_abs_path(
23661                PathBuf::from(path!("/a/first.rs")),
23662                OpenOptions::default(),
23663                window,
23664                cx,
23665            )
23666        })
23667        .unwrap()
23668        .await
23669        .unwrap()
23670        .downcast::<Editor>()
23671        .unwrap();
23672    let fake_server = fake_servers.next().await.unwrap();
23673    let server_id = fake_server.server.server_id();
23674    let mut first_request = fake_server
23675        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23676            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23677            let result_id = Some(new_result_id.to_string());
23678            assert_eq!(
23679                params.text_document.uri,
23680                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23681            );
23682            async move {
23683                Ok(lsp::DocumentDiagnosticReportResult::Report(
23684                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23685                        related_documents: None,
23686                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23687                            items: Vec::new(),
23688                            result_id,
23689                        },
23690                    }),
23691                ))
23692            }
23693        });
23694
23695    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23696        project.update(cx, |project, cx| {
23697            let buffer_id = editor
23698                .read(cx)
23699                .buffer()
23700                .read(cx)
23701                .as_singleton()
23702                .expect("created a singleton buffer")
23703                .read(cx)
23704                .remote_id();
23705            let buffer_result_id = project
23706                .lsp_store()
23707                .read(cx)
23708                .result_id(server_id, buffer_id, cx);
23709            assert_eq!(expected, buffer_result_id);
23710        });
23711    };
23712
23713    ensure_result_id(None, cx);
23714    cx.executor().advance_clock(Duration::from_millis(60));
23715    cx.executor().run_until_parked();
23716    assert_eq!(
23717        diagnostic_requests.load(atomic::Ordering::Acquire),
23718        1,
23719        "Opening file should trigger diagnostic request"
23720    );
23721    first_request
23722        .next()
23723        .await
23724        .expect("should have sent the first diagnostics pull request");
23725    ensure_result_id(Some("1".to_string()), cx);
23726
23727    // Editing should trigger diagnostics
23728    editor.update_in(cx, |editor, window, cx| {
23729        editor.handle_input("2", window, cx)
23730    });
23731    cx.executor().advance_clock(Duration::from_millis(60));
23732    cx.executor().run_until_parked();
23733    assert_eq!(
23734        diagnostic_requests.load(atomic::Ordering::Acquire),
23735        2,
23736        "Editing should trigger diagnostic request"
23737    );
23738    ensure_result_id(Some("2".to_string()), cx);
23739
23740    // Moving cursor should not trigger diagnostic request
23741    editor.update_in(cx, |editor, window, cx| {
23742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23743            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23744        });
23745    });
23746    cx.executor().advance_clock(Duration::from_millis(60));
23747    cx.executor().run_until_parked();
23748    assert_eq!(
23749        diagnostic_requests.load(atomic::Ordering::Acquire),
23750        2,
23751        "Cursor movement should not trigger diagnostic request"
23752    );
23753    ensure_result_id(Some("2".to_string()), cx);
23754    // Multiple rapid edits should be debounced
23755    for _ in 0..5 {
23756        editor.update_in(cx, |editor, window, cx| {
23757            editor.handle_input("x", window, cx)
23758        });
23759    }
23760    cx.executor().advance_clock(Duration::from_millis(60));
23761    cx.executor().run_until_parked();
23762
23763    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23764    assert!(
23765        final_requests <= 4,
23766        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23767    );
23768    ensure_result_id(Some(final_requests.to_string()), cx);
23769}
23770
23771#[gpui::test]
23772async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23773    // Regression test for issue #11671
23774    // Previously, adding a cursor after moving multiple cursors would reset
23775    // the cursor count instead of adding to the existing cursors.
23776    init_test(cx, |_| {});
23777    let mut cx = EditorTestContext::new(cx).await;
23778
23779    // Create a simple buffer with cursor at start
23780    cx.set_state(indoc! {"
23781        ˇaaaa
23782        bbbb
23783        cccc
23784        dddd
23785        eeee
23786        ffff
23787        gggg
23788        hhhh"});
23789
23790    // Add 2 cursors below (so we have 3 total)
23791    cx.update_editor(|editor, window, cx| {
23792        editor.add_selection_below(&Default::default(), window, cx);
23793        editor.add_selection_below(&Default::default(), window, cx);
23794    });
23795
23796    // Verify we have 3 cursors
23797    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23798    assert_eq!(
23799        initial_count, 3,
23800        "Should have 3 cursors after adding 2 below"
23801    );
23802
23803    // Move down one line
23804    cx.update_editor(|editor, window, cx| {
23805        editor.move_down(&MoveDown, window, cx);
23806    });
23807
23808    // Add another cursor below
23809    cx.update_editor(|editor, window, cx| {
23810        editor.add_selection_below(&Default::default(), window, cx);
23811    });
23812
23813    // Should now have 4 cursors (3 original + 1 new)
23814    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23815    assert_eq!(
23816        final_count, 4,
23817        "Should have 4 cursors after moving and adding another"
23818    );
23819}
23820
23821#[gpui::test(iterations = 10)]
23822async fn test_document_colors(cx: &mut TestAppContext) {
23823    let expected_color = Rgba {
23824        r: 0.33,
23825        g: 0.33,
23826        b: 0.33,
23827        a: 0.33,
23828    };
23829
23830    init_test(cx, |_| {});
23831
23832    let fs = FakeFs::new(cx.executor());
23833    fs.insert_tree(
23834        path!("/a"),
23835        json!({
23836            "first.rs": "fn main() { let a = 5; }",
23837        }),
23838    )
23839    .await;
23840
23841    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23842    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23843    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23844
23845    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23846    language_registry.add(rust_lang());
23847    let mut fake_servers = language_registry.register_fake_lsp(
23848        "Rust",
23849        FakeLspAdapter {
23850            capabilities: lsp::ServerCapabilities {
23851                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23852                ..lsp::ServerCapabilities::default()
23853            },
23854            name: "rust-analyzer",
23855            ..FakeLspAdapter::default()
23856        },
23857    );
23858    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23859        "Rust",
23860        FakeLspAdapter {
23861            capabilities: lsp::ServerCapabilities {
23862                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23863                ..lsp::ServerCapabilities::default()
23864            },
23865            name: "not-rust-analyzer",
23866            ..FakeLspAdapter::default()
23867        },
23868    );
23869
23870    let editor = workspace
23871        .update(cx, |workspace, window, cx| {
23872            workspace.open_abs_path(
23873                PathBuf::from(path!("/a/first.rs")),
23874                OpenOptions::default(),
23875                window,
23876                cx,
23877            )
23878        })
23879        .unwrap()
23880        .await
23881        .unwrap()
23882        .downcast::<Editor>()
23883        .unwrap();
23884    let fake_language_server = fake_servers.next().await.unwrap();
23885    let fake_language_server_without_capabilities =
23886        fake_servers_without_capabilities.next().await.unwrap();
23887    let requests_made = Arc::new(AtomicUsize::new(0));
23888    let closure_requests_made = Arc::clone(&requests_made);
23889    let mut color_request_handle = fake_language_server
23890        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23891            let requests_made = Arc::clone(&closure_requests_made);
23892            async move {
23893                assert_eq!(
23894                    params.text_document.uri,
23895                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23896                );
23897                requests_made.fetch_add(1, atomic::Ordering::Release);
23898                Ok(vec![
23899                    lsp::ColorInformation {
23900                        range: lsp::Range {
23901                            start: lsp::Position {
23902                                line: 0,
23903                                character: 0,
23904                            },
23905                            end: lsp::Position {
23906                                line: 0,
23907                                character: 1,
23908                            },
23909                        },
23910                        color: lsp::Color {
23911                            red: 0.33,
23912                            green: 0.33,
23913                            blue: 0.33,
23914                            alpha: 0.33,
23915                        },
23916                    },
23917                    lsp::ColorInformation {
23918                        range: lsp::Range {
23919                            start: lsp::Position {
23920                                line: 0,
23921                                character: 0,
23922                            },
23923                            end: lsp::Position {
23924                                line: 0,
23925                                character: 1,
23926                            },
23927                        },
23928                        color: lsp::Color {
23929                            red: 0.33,
23930                            green: 0.33,
23931                            blue: 0.33,
23932                            alpha: 0.33,
23933                        },
23934                    },
23935                ])
23936            }
23937        });
23938
23939    let _handle = fake_language_server_without_capabilities
23940        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23941            panic!("Should not be called");
23942        });
23943    cx.executor().advance_clock(Duration::from_millis(100));
23944    color_request_handle.next().await.unwrap();
23945    cx.run_until_parked();
23946    assert_eq!(
23947        1,
23948        requests_made.load(atomic::Ordering::Acquire),
23949        "Should query for colors once per editor open"
23950    );
23951    editor.update_in(cx, |editor, _, cx| {
23952        assert_eq!(
23953            vec![expected_color],
23954            extract_color_inlays(editor, cx),
23955            "Should have an initial inlay"
23956        );
23957    });
23958
23959    // opening another file in a split should not influence the LSP query counter
23960    workspace
23961        .update(cx, |workspace, window, cx| {
23962            assert_eq!(
23963                workspace.panes().len(),
23964                1,
23965                "Should have one pane with one editor"
23966            );
23967            workspace.move_item_to_pane_in_direction(
23968                &MoveItemToPaneInDirection {
23969                    direction: SplitDirection::Right,
23970                    focus: false,
23971                    clone: true,
23972                },
23973                window,
23974                cx,
23975            );
23976        })
23977        .unwrap();
23978    cx.run_until_parked();
23979    workspace
23980        .update(cx, |workspace, _, cx| {
23981            let panes = workspace.panes();
23982            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23983            for pane in panes {
23984                let editor = pane
23985                    .read(cx)
23986                    .active_item()
23987                    .and_then(|item| item.downcast::<Editor>())
23988                    .expect("Should have opened an editor in each split");
23989                let editor_file = editor
23990                    .read(cx)
23991                    .buffer()
23992                    .read(cx)
23993                    .as_singleton()
23994                    .expect("test deals with singleton buffers")
23995                    .read(cx)
23996                    .file()
23997                    .expect("test buffese should have a file")
23998                    .path();
23999                assert_eq!(
24000                    editor_file.as_ref(),
24001                    Path::new("first.rs"),
24002                    "Both editors should be opened for the same file"
24003                )
24004            }
24005        })
24006        .unwrap();
24007
24008    cx.executor().advance_clock(Duration::from_millis(500));
24009    let save = editor.update_in(cx, |editor, window, cx| {
24010        editor.move_to_end(&MoveToEnd, window, cx);
24011        editor.handle_input("dirty", window, cx);
24012        editor.save(
24013            SaveOptions {
24014                format: true,
24015                autosave: true,
24016            },
24017            project.clone(),
24018            window,
24019            cx,
24020        )
24021    });
24022    save.await.unwrap();
24023
24024    color_request_handle.next().await.unwrap();
24025    cx.run_until_parked();
24026    assert_eq!(
24027        3,
24028        requests_made.load(atomic::Ordering::Acquire),
24029        "Should query for colors once per save and once per formatting after save"
24030    );
24031
24032    drop(editor);
24033    let close = workspace
24034        .update(cx, |workspace, window, cx| {
24035            workspace.active_pane().update(cx, |pane, cx| {
24036                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24037            })
24038        })
24039        .unwrap();
24040    close.await.unwrap();
24041    let close = workspace
24042        .update(cx, |workspace, window, cx| {
24043            workspace.active_pane().update(cx, |pane, cx| {
24044                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24045            })
24046        })
24047        .unwrap();
24048    close.await.unwrap();
24049    assert_eq!(
24050        3,
24051        requests_made.load(atomic::Ordering::Acquire),
24052        "After saving and closing all editors, no extra requests should be made"
24053    );
24054    workspace
24055        .update(cx, |workspace, _, cx| {
24056            assert!(
24057                workspace.active_item(cx).is_none(),
24058                "Should close all editors"
24059            )
24060        })
24061        .unwrap();
24062
24063    workspace
24064        .update(cx, |workspace, window, cx| {
24065            workspace.active_pane().update(cx, |pane, cx| {
24066                pane.navigate_backward(window, cx);
24067            })
24068        })
24069        .unwrap();
24070    cx.executor().advance_clock(Duration::from_millis(100));
24071    cx.run_until_parked();
24072    let editor = workspace
24073        .update(cx, |workspace, _, cx| {
24074            workspace
24075                .active_item(cx)
24076                .expect("Should have reopened the editor again after navigating back")
24077                .downcast::<Editor>()
24078                .expect("Should be an editor")
24079        })
24080        .unwrap();
24081    color_request_handle.next().await.unwrap();
24082    assert_eq!(
24083        3,
24084        requests_made.load(atomic::Ordering::Acquire),
24085        "Cache should be reused on buffer close and reopen"
24086    );
24087    editor.update(cx, |editor, cx| {
24088        assert_eq!(
24089            vec![expected_color],
24090            extract_color_inlays(editor, cx),
24091            "Should have an initial inlay"
24092        );
24093    });
24094}
24095
24096#[gpui::test]
24097async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24098    init_test(cx, |_| {});
24099    let (editor, cx) = cx.add_window_view(Editor::single_line);
24100    editor.update_in(cx, |editor, window, cx| {
24101        editor.set_text("oops\n\nwow\n", window, cx)
24102    });
24103    cx.run_until_parked();
24104    editor.update(cx, |editor, cx| {
24105        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24106    });
24107    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24108    cx.run_until_parked();
24109    editor.update(cx, |editor, cx| {
24110        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24111    });
24112}
24113
24114#[track_caller]
24115fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24116    editor
24117        .all_inlays(cx)
24118        .into_iter()
24119        .filter_map(|inlay| inlay.get_color())
24120        .map(Rgba::from)
24121        .collect()
24122}